diff --git a/.claude/commands/implement-extensions-batch.md b/.claude/commands/implement-extensions-batch.md index bfb0b851..6f07f0e7 100644 --- a/.claude/commands/implement-extensions-batch.md +++ b/.claude/commands/implement-extensions-batch.md @@ -1,39 +1,28 @@ --- -description: Spawn the 4-role team across N SysML2.NET Extend files in one run — creates and pushes a batch branch, assigns the related GitHub issues to the user, and updates each issue's checklist on completion +description: Spawn the 4-role team across N SysML2.NET Extend files in one run — creates a batch branch, assigns the related GitHub issues to the user, and updates each issue's checklist on completion argument-hint: [ ...] (2–6 Extension file names; each will be normalised to SysML2.NET/Extend/Extensions.cs) --- # /implement-extensions-batch -Run **ONE** 4-role team (researcher, implementer, tester, reviewer) over N -files (`$ARGUMENTS`) in a single batch. Each agent handles **all N files** in -its own context — not one team per file. Role boundaries are unchanged: the -implementer still cannot touch test fixtures, the tester still cannot touch -production files, the reviewer is still read-only. What changes is the ACL -size: each role's allowed-write set is now the N files of its kind in the -batch instead of one. - -What this command does on top of the single-file flow: +Apply the **existing `/implement-extensions` 4-role team workflow** across N +files (`$ARGUMENTS`) in one run. The team itself is unchanged — researcher, +implementer, tester, reviewer per file. What this command adds on top of the +single-file flow: 1. **Pre-flight validation** of every file + its GitHub issue, before any state change. 2. **Creates a new git branch** off `development` with a deterministic name - derived from the batch's issue numbers, AND pushes it to `origin` with - upstream tracking set so the user can immediately open a pull request after - committing. + derived from the batch's issue numbers. 3. **Assigns every related GitHub issue to the invoking user** (`@me`). -4. **Spawns one team via `TeamCreate` + 4 named agents.** Researcher first, - then implementer + tester in parallel, then reviewer. Iterative fixes route - via `SendMessage` to the same named agent — no fresh spawns. -5. **Single consolidated regression sweep** dispatched to the still-running - tester. +4. **Parallelises agent spawns across files** wherever their target files are + disjoint. +5. **Single consolidated regression sweep** instead of one per file. 6. **Loops the issue-checklist sync** per file at the end. The team template (role prompts) at `.claude/team-templates/extension-impl.md` -is the source of truth for both the single-file role prompts AND the -batch-mode addenda. This command body is the batch orchestration glue. - -**Total live-agent count: 4** (vs. 4N in the previous per-file design). +(v2, repo-tracked) is the source of truth for the per-file behaviour. This +command body is the batch orchestration glue. ## Path conventions @@ -49,7 +38,7 @@ Repo-relative with forward slashes throughout. Tools that require absolute paths - The researcher notes file per batch member: `.team-notes/-extensions-spec.md`. - Sibling test fixtures whose `Throws.TypeOf()` assertions now fail because one of the batch's implementations unblocked them (consolidated - regression sweep, see step 12). + regression sweep, see step 10). **MUST NOT modify** the same things the single-file command refuses to touch: other production files in `SysML2.NET/Extend/` or `SysML2.NET/Core/`, @@ -60,36 +49,6 @@ stub-blocker test pattern (see template) when an in-scope test would otherwise need to traverse a still-stubbed upstream method that is NOT part of the current batch. -## Pre-flight: plan mode IS the pre-execution approval gate (Gate 0) - -If the orchestrator session is in **plan mode** when `/implement-extensions-batch` is invoked, use plan mode as the natural pre-execution approval gate. Do NOT spawn any sub-agent, do NOT create any branch, do NOT assign any issue. Instead: - -1. **Stay in plan mode.** Do all the READ-ONLY pre-flight work that steps 1 → 4 of this command require: - - Validate every input file exists; refuse missing files. - - For each file, look up the GitHub issue via `gh issue list … --search " in:body"`. Surface ambiguities (0 or >1 match) via `AskUserQuestion`. - - Enumerate stub `Compute*` methods per file (`Grep "throw new NotSupportedException"`); drop files with zero stubs. - - Grade complexity per file (step 3.5 rubric); roll up per-role models (step 3.5 rollup logic). - - Read `git status --porcelain` (refuse on dirty tree). - - `git fetch origin development` (read-only). - - Run `git ls-remote --exit-code origin ` and `git branch --list ` to check the branch isn't already taken. If it is, surface via `AskUserQuestion` (different branch / abort). - -2. **Write the plan file** (at the path Claude Code provides in the plan-mode system reminder) with the standard structure (Context, Recommended approach, Critical files, Verification). The "Recommended approach" section is a concise description of the proposed execution: - - Batch composition (files + GitHub issue numbers + per-file complexity grade + stub counts). - - Proposed branch name + base ref + remote-tracking intent. - - Team name (`TeamCreate({team_name: "..."})`). - - Per-role model picks (rolled up from step 3.5). Note that `ExitPlanMode` is binary (approve / reject), so the user cannot override models from inside the plan-approval UI. Two override paths are supported: (a) reject the plan and re-invoke with an explicit model preference in the prompt, OR (b) accept the plan and the orchestrator fires a follow-up `AskUserQuestion` (Yes / "all Sonnet" / "all Opus" / Custom) BEFORE step 6's branch creation. - - Workflow shape: Phase R → Phase R-A (Gate R-A approval) → Phase IT → V → S → RV → IS. - - Explicit mention that Phase R-A will pause for `AskUserQuestion` after the researcher returns its `spec ready` (so the user knows there is exactly ONE more structural checkpoint after this one). - -3. **Call `ExitPlanMode`.** The user reviews the plan in the standard plan-approval UI: - - **Approve (unchanged)** → plan mode exits, orchestrator proceeds (model-override `AskUserQuestion` per step 2 above, then step 6 → 7 → 8 → Phase R → Phase R-A → Phase IT → V → S → RV → IS). - - **Approve (edited)** → the tool result includes a `## Approved Plan (edited by user):` block with the user's modifications. The orchestrator MUST treat the EDITED plan content as the source of truth — re-parse composition / branch / models from it, NOT from the originally-proposed plan. If the user dropped a file by removing it from the composition list, the orchestrator continues with the reduced set. If the user changed the branch name or model picks inline, the orchestrator honors that. (Standard Claude Code plan-edit-on-approval pattern; see the `` after every `ExitPlanMode` approval.) - - **Reject** → nothing executes. No branch, no team, no issue assignments. The plan file remains for the user's reference. - -If the orchestrator session is **NOT in plan mode** when the command is invoked, continue with the existing step 5 sanity-check (`AskUserQuestion` for composition + models + branch name). Gate 0 only fires in plan mode; otherwise the existing step 5 plays the same role. - -**Important — tool-level prompts are NOT auto-allowed.** Gate 0 (and Gate R-A in step 9.5) govern STRUCTURAL approval only. Individual tool calls — `Bash(dotnet build *)`, `Bash(git push *)`, `TeamCreate`, `Agent(...)`, etc. — continue to surface per the user's `settings.json` and harness defaults. The user has chosen to keep handling those prompts manually; the gates do not bypass them. - ## Workflow ### 1. Parse `$ARGUMENTS` and validate the batch @@ -141,30 +100,10 @@ For each file in the (possibly reduced) batch: enclosing methods. If 0, drop the file from the batch and inform the user (already implemented). - Apply the same complexity-grading rubric from `/implement-extensions` step 3.5 - to that file's method list. Record `(complexity, per_file_per_role_model_picks)`. + to that file's method list. Record `(complexity, per_role_model_picks)`. If the batch becomes empty after pruning, abort cleanly. -### 3.5. Batch-wide model rollup (one model per role for the whole team) - -Each role is spawned ONCE for the whole batch, so the orchestrator picks ONE -model per role using the **worst-case complexity across the batch**: - -- If any file in the batch is graded `complex` → that role's complex-tier model. -- Else if any file is graded `standard` → that role's standard-tier model. -- Else (all `trivial`) → that role's trivial-tier model. - -Rationale: the per-role model is fixed at spawn time and applies uniformly to -every file the agent edits in that run. Picking a tier weaker than the worst -file would under-equip the agent on that file. The user can still override -"all Sonnet" / "all Opus" / "Custom" at the step-5 sanity check. - -Per-role asymmetry across roles is still encouraged (e.g. trivial impl across -all files but a heavy regression-sweep load → Opus tester). - -Record the rolled-up picks as -`(batch_researcher_model, batch_implementer_model, batch_tester_model, batch_reviewer_model)`. - ### 4. Pre-flight git checks - `git status --porcelain` must be empty. Refuse to proceed otherwise — the user @@ -175,16 +114,13 @@ Record the rolled-up picks as Use `AskUserQuestion` to present: -- The final batch composition (files + issues + per-file complexity grade for - transparency). -- The **batch-wide rolled-up picks** from step 3.5: - `researcher=, implementer=, tester=, reviewer=`. +- The final batch composition (files + issues + complexity + per-role model + picks per file). - The proposed branch name (see step 6). - Questions: 1. **Proceed with this batch composition?** (Yes / No / drop specific files) - 2. **Use the rolled-up batch-wide model selection?** (Yes / override "all - Sonnet" / "all Opus" / custom — Custom lets the user override individual - roles) + 2. **Use the per-file dynamic model selection?** (Yes / override "all Sonnet" + / "all Opus" / custom) If user picks "drop specific files" or overrides models, apply and re-confirm. @@ -202,25 +138,11 @@ batch-impl-extensions- - If more than 4 issues: include the first 4 + `-plus` suffix (e.g. `batch-impl-extensions-123-180-186-190-plus2` for N=6). -Create locally **and immediately publish to `origin` with upstream tracking**: +Create: ```bash git switch -c origin/development -git push -u origin ``` -The push lifts the branch onto the remote at the same commit as -`origin/development` (no diff yet — that comes after the batch's edits + the -user's commit). Setting upstream now means: -- The user's eventual `git push` after committing needs no flags. -- A pull request can be opened via the GitHub UI or `gh pr create` as soon as - the user pushes their first commit, without an additional `git push -u` - step. - -If the `git push -u` fails (network, auth, branch-protection refusing empty -pushes), log the failure but **continue with the batch**. The implementation -work is the main goal; the branch will still exist locally and the user can -re-push manually at the end. Surface the failure clearly in the final summary. - Refuse if the branch already exists locally OR on origin (`git ls-remote --exit-code origin `) — ask the user to pick a different batch or delete the stale branch. @@ -237,102 +159,41 @@ Idempotent — re-assigning is a no-op on `gh`. Report success/failure per issue on failure, log and continue (an unassignable issue is not a blocker for the implementation itself). -### 8. Create the named team (one-shot, before any Phase) - -``` -TeamCreate({team_name: "batch-extensions-impl-"}) -``` - -where `` is the same dashed-issue-number string used in the -branch name from step 6 (deduplicates across concurrent batches). The team -hosts the four named teammates spawned in the following phases. - -Set `{{ORCHESTRATOR_NAME}}` = your own orchestrator identifier (the value -named teammates SendMessage back to). The named teammates remain addressable -for the rest of the run; iterative fixes route via `SendMessage to: ""`, -not via fresh `Agent` spawns. - -### 9. Phase R — Spawn the single researcher +### 8. Phase R — Spawn researchers in parallel -ONE `Agent(...)` call: +**One orchestrator message containing N `Agent(...)` calls** (one per file). Each: -- `name: "researcher"` - `subagent_type: "general-purpose"` -- `team_name`: the team from step 8. -- `model`: `` from step 3.5 (or user override from step 5). +- `model: ` per the per-file step-3 grade (Haiku trivial / + Sonnet standard / Opus complex, or user override from step 5). - Foreground (no `run_in_background`). -- Prompt: the **batch-mode researcher prompt** from - `.claude/team-templates/extension-impl.md` — i.e. the single-file researcher - body PLUS the "Batch-mode operation" addendum, with `{{BATCH_FILES}}` - expanded to the numbered list of (interface, paths, method-list) tuples, - one per file in the batch. +- Prompt: the v2 researcher prompt from `.claude/team-templates/extension-impl.md` + with that file's `{{PLACEHOLDERS}}` substituted + the file's method list. -Wait for the researcher's `spec ready` SendMessage. Then **read each `.team-notes/-extensions-spec.md`** yourself to verify coverage, spec-text-only flags, and stub-blocker flags. Proceed to step 9.5 (Phase R-A approval gate) — DO NOT spawn implementer + tester directly. +After all N return, **read each notes file** yourself to verify coverage + +spec-text-only flags + stub-blocker flags. -The researcher agent **stays addressable** for the rest of the run — the implementer / tester / reviewer may need a clarification on one file's OCL later, which the orchestrator can route via `SendMessage to: "researcher"`. The researcher is also the recipient of step 9.5's "Abort — research again with feedback" branch. +### 9. Phase IT — Spawn implementers + testers in parallel -### 9.5. Phase R-A — Researcher-plan approval gate (MANDATORY, every run) +**One orchestrator message containing 2N `Agent(...)` calls** — one implementer ++ one tester per file. All foreground. -Before spawning implementer + tester, the orchestrator MUST render an inline spec preview and ask for explicit approval. This is non-skippable, even when the researcher reports zero ambiguities / zero spec-text-only flags / zero stub-blockers. It is the only chance the user gets to inspect the per-method derivation plan before any code is written to disk. +Each implementer prompt is the v2 implementer prompt with the file's placeholders ++ the **parallel-mode caveat** clearly stated (see template). Each tester prompt +is the v2 tester prompt with the same caveat — they MUST run `dotnet build` only +and MUST NOT run `dotnet test` (production lacks parallel-turn edits in their +disk view). -1. **Render** an inline preview in the chat response, one block per file. For each file, pull from its `.team-notes/-extensions-spec.md`: - - File path + GitHub issue number (link to issue if practical). - - Per method: - - Signature line (`internal static Compute(this I )`). - - Derivation source tag: `OCL in XMI`, `OCL in `, or `spec-text only`. - - The suggested C# code block from the notes — single fenced block, cap ≤ 8 lines. - - Dependencies summary (sibling derived properties used; upstream stubs hit). - - Stub-blocker flag (if any) — signals that the populated case will need the stub-blocker test pattern. - - Use a compact Markdown format. Cap total preview at ~80 lines per file to stay scannable. If a notes file's suggested code is unusually long, link the notes-file path and quote only the first ~5 lines. +### 10. Phase V — Orchestrator verification (sequential) -2. **Ask** via `AskUserQuestion` (single question, 3 options): - - **Approve — spawn implementer + tester now** *(Recommended)*. Proceeds to step 10. - - **Drop specific files from the batch**. User picks which file(s) to exclude; orchestrator regenerates the preview against the remaining files and re-asks. Drop-and-continue does NOT recreate the branch (already pushed) and does NOT unassign the dropped issues (idempotent enough; surface this in the final summary). - - **Abort — research again with feedback**. Orchestrator forwards the user's free-form `Other` text to the still-addressable `researcher` via `SendMessage`, waits for a fresh `spec ready`, then re-runs step 9.5. - -3. **On Approve**, proceed to step 10. Do NOT re-ask until the run completes. - -4. **On Abort (user rejects without requesting re-research)**, stop the orchestration. Branch + issue assignments remain. Notes files remain. The user can revert manually via git if desired. - -Rationale: the previous "surface ambiguities" instruction was conditional and got skipped on clean researcher returns. This gate runs *every time*, so the user always sees the researcher's contract before any code is committed to disk. The gate governs STRUCTURAL approval only — individual tool-permission prompts that the user's `settings.json` requires (e.g. `Bash(dotnet build *)`) continue to surface during Phase IT / V / S / RV / IS. - -### 10. Phase IT — Spawn the single implementer + single tester in parallel - -**One orchestrator message containing TWO `Agent(...)` calls**, both -foreground: - -1. Implementer: - - `name: "implementer"`, `team_name`: same team. - - `model`: ``. - - Prompt: batch-mode implementer prompt with `{{BATCH_FILES}}` expanded. -2. Tester: - - `name: "tester"`, `team_name`: same team. - - `model`: ``. - - Prompt: batch-mode tester prompt with `{{BATCH_FILES}}` expanded AND - the **parallel-mode caveat** clearly stated (`dotnet build` only; - MUST NOT run `dotnet test`). - -The two agents edit disjoint files (N production files vs. N test fixtures), -so concurrent edits are safe. - -Wait for both `dev complete` and `tests complete` SendMessages. - -### 11. Phase V — Orchestrator verification (sequential) - -Run sequentially in the orchestrator turn: +After all 2N agents return, run sequentially in the orchestrator turn: 1. **One build of production**: ```bash dotnet build SysML2.NET/SysML2.NET.csproj --nologo --verbosity quiet ``` - On failure, attribute the error to its source file(s) by reading the - compiler output, then: - ``` - SendMessage({to: "implementer", message: "Build failed; fix in place. - Files + errors: …"}) - ``` - The same implementer agent retains its context and patches the diffs. - Iterate until the build is green. + On failure, identify which file's production diff caused it, re-dispatch + that file's implementer. Iterate. 2. **One consolidated targeted test run**, OR-joining every fixture in the batch: ```bash @@ -340,17 +201,12 @@ Run sequentially in the orchestrator turn: --filter "FullyQualifiedName~ExtensionsTestFixture|FullyQualifiedName~ExtensionsTestFixture|..." \ --nologo --verbosity quiet ``` - For each failure, attribute it and route via `SendMessage`: - - **OCL mistranslation in production** → - `SendMessage({to: "implementer", message: "(file, method, - observed-vs-expected). Fix in place."})` - - **Wrong test assertion** → - `SendMessage({to: "tester", message: "(file, method, - observed-vs-expected). Fix in place."})` - Both agents keep their context; do NOT spawn fresh per-fix Agent calls. + For each failure, attribute it to the correct file: + - OCL mistranslation in production → re-dispatch THAT file's implementer. + - Wrong test assertion → re-dispatch THAT file's tester. Iterate until 0 failures across the batch. -### 12. Phase S — Consolidated regression sweep +### 11. Phase S — Consolidated regression sweep ```bash dotnet test SysML2.NET.sln --no-build --nologo --verbosity quiet @@ -360,44 +216,24 @@ For each `Expected: But was: no exception` failure, identify which file in the batch unblocked it (grep the failing test for `For Later: depends on …` references; or trace by the targeted stub's signature). -Send the consolidated brief to the still-running tester: - -``` -SendMessage({to: "tester", message: "Regression sweep brief. For each -sibling fixture below, expand-don't-replace per the four-axis checklist -(filter discrimination + predicate completeness + owned vs inherited + -null-projection guard). Sibling fixtures and their failing tests: -- SysML2.NET.Tests/Extend/ExtensionsTestFixture.cs: - failing tests: [...], exercising production OCL: ..."}) -``` - -The tester's ACL extends to those sibling fixtures for this dispatch only. +Dispatch regression-sweep testers (Sonnet by default) per touched sibling +fixture, in parallel when the sibling fixtures are disjoint. Use the +**expand-don't-replace** brief from `/implement-extensions` step 8 (filter +discrimination + predicate completeness + owned vs inherited + null-projection +guard). Iterate until the full solution test run is 0 failures. -### 13. Phase RV — Spawn the single reviewer - -ONE `Agent(...)` call: +### 12. Phase RV — Reviewers in parallel -- `name: "reviewer"`, `team_name`: same team, `model: `. -- Prompt: batch-mode reviewer prompt with `{{BATCH_FILES}}` expanded. +**One orchestrator message containing N `Agent(...)` calls** — one reviewer per +file. Each scoped to ONE file's `(notes file, production file, test fixture, +regression-swept sibling tests that this file's implementation touched)`. -The reviewer walks each file's `(notes, production, tests + any -regression-swept sibling fixtures touched by this batch)` triple and returns -one `OK` or `NEEDS FIX` verdict with per-file findings grouped. +For each "NEEDS FIX" verdict, dispatch the implementer or tester for that file +back. Other files' results are still reported in the final summary. -For each NEEDS-FIX finding: -- Production-code finding → - `SendMessage({to: "implementer", message: "(file, line, concern). Fix."})` -- Test finding → - `SendMessage({to: "tester", message: "(file, line, concern). Fix."})` - -Re-run Phase V's build + consolidated targeted test after fixes, then -re-dispatch the reviewer if its prior verdict was NEEDS FIX: -`SendMessage({to: "reviewer", message: "Findings actioned; please -re-verify."})`. The reviewer keeps its context. - -### 14. Phase IS — Issue checklist sync (sequential, looped) +### 13. Phase IS — Issue checklist sync (sequential, looped) For each `(file, issue_number)` pair in the batch, run the **identical** step-11 logic from `/implement-extensions`: @@ -406,21 +242,18 @@ step-11 logic from `/implement-extensions`: 2. Locate `### Checklist` section. 3. Enumerate `Compute*` methods in the production file. Tick the ones whose bodies no longer throw `NotSupportedException` AND whose `Verify{Method}` - passed in step 11's last `dotnet test` run. + passed in step 10's last `dotnet test` run. 4. Append any new methods (signature not present in existing checklist) in declaration order. 5. Stitch new body (touch ONLY the Checklist section). 6. Push via `gh issue edit --body-file `. 7. Re-fetch + diff to verify only the Checklist section changed. -### 15. Final summary +### 14. Final summary Print to the user: -- **Branch**: name + base ref + remote-tracking state (`pushed to origin` / - `local only — push failed at step 6, push manually with: git push -u origin `) - + how to delete-if-aborting (locally: `git branch -D `; remotely if - pushed: `git push origin --delete `). +- **Branch**: name + base ref + how to delete-if-aborting. - **Per-file table**: | File | Stubs impl. | Targeted tests | Reg. sweep impact | Reviewer | Issue | @@ -436,11 +269,21 @@ Print to the user: - Out-of-scope blockers surfaced (e.g. "VerifyComputeX in TestFixture is still stub-blocked on `` — consider a follow-up issue"). -- **Reminder**: nothing is auto-committed. The branch exists locally (and on - `origin` with upstream tracking, when step 6's push succeeded). User reviews - `git diff`, commits, then `git push` (no flags needed — tracking is already - set) and opens the PR via `gh pr create --base development --head ` - or the GitHub UI. +- **Pre-filled commit message** (MANDATORY — append at the very end of the + final-summary message in a fenced code block, ready to copy): + + ``` + Fix # # # … + ``` + + Where the numbers are exactly the GitHub issue numbers handled by this + batch, in the original `$ARGUMENTS` order (or, if the user expressed a + preferred order in the invocation prompt, that order). Nothing else — + no body paragraphs, no per-file bullet list, no `Co-Authored-By` trailer, + no "🤖 Generated with …" footer. The single line is the entire message. + +- **Reminder**: nothing is auto-committed. User reviews `git diff`, decides + whether to commit / push / open PR. ## Failure handling @@ -451,60 +294,38 @@ Print to the user: | Ambiguous issue | Step 2 | `AskUserQuestion` for an explicit issue number per file. | | Dirty working tree | Step 4 | Abort, ask user to commit/stash. | | Branch already exists | Step 6 | Abort; ask user to pick a different batch or delete the stale branch. | -| `git push -u origin ` fails (network, auth, branch protection) | Step 6 | Log + continue (non-blocking; surface clearly in step 15 final summary with a manual re-push command). | | `gh issue edit --add-assignee` fails for one issue | Step 7 | Log + continue (non-blocking; implementation still proceeds). | -| `TeamCreate` fails | Step 8 | Abort with a clear error; the named-teammate flow requires a live team. Do NOT silently fall back to per-file `Agent` calls — that defeats the cost-reduction goal of this command. | -| Production build fails after implementer | Step 11.1 | Attribute to the source file(s); `SendMessage to: "implementer"` with the failing file + error. Same agent, no re-spawn. | -| Targeted test fails | Step 11.2 | Attribute (OCL vs test bug); `SendMessage to: "implementer"` or `to: "tester"` with the `(file, method, observed-vs-expected)` triple. Same agent, no re-spawn. | -| Sibling test failure in regression sweep | Step 12 | `SendMessage to: "tester"` with the consolidated regression brief (all touched sibling fixtures in one dispatch). Tester's ACL extends to those fixtures for this dispatch only. | -| Reviewer NEEDS FIX | Step 13 | `SendMessage to: "implementer"` or `to: "tester"` with the per-file finding; then `SendMessage to: "reviewer"` to re-verify. Same agents throughout. | -| Named teammate becomes unresponsive (timed-out SendMessage) | Phases IT / V / S / RV | Fall back to a fresh `Agent(...)` spawn for that role only, replaying the spawn prompt PLUS the most recent context the orchestrator has (notes file paths, prior deviation reports). The team-name stays alive for the other roles. | -| Sub-agent deadlocked on harness UI permission prompt the user cannot action | Phases IT / V / S / RV | Orchestrator can take over the deadlocked sub-agent's remaining work directly (build / test / gh issue edit run from the orchestrator's own permission scope). Send `shutdown_request` to the parked sub-agent. Surface to the user clearly in the final summary. (Precedent: 2026-05-28 batch run for issues #111/#116/#118/#119/#130/#132 — implementer + tester parked on `dotnet build` prompts; orchestrator completed Phases V → S → RV → IS itself.) | +| Production build fails after implementer | Step 10.1 | Attribute to the file, re-dispatch that implementer. | +| Targeted test fails | Step 10.2 | Attribute (OCL vs test bug), re-dispatch correct role. | +| Sibling test failure in regression sweep | Step 11 | Dispatch a regression-sweep tester per touched fixture; in scope. | +| Reviewer NEEDS FIX | Step 12 | Re-dispatch implementer or tester for that file only; other files' results still reported. | | One file's implementation fails after branch + assignment | Any step ≥ 6 | Keep the branch; surface in final summary; user decides whether to retry via `/implement-extensions` for that single file or revert. | -| User rejects plan at Gate 0 (Pre-flight, plan-mode invocation) | Pre-flight | Stop. No branch, no team, no issue assignments. Plan file remains for reference. | -| User picks "Abort — research again with feedback" at Gate R-A (Phase R-A) | Step 9.5 | Forward the user's free-form text to the still-addressable `researcher` via `SendMessage`. Wait for a fresh `spec ready`. Re-run step 9.5. | -| User picks "Abort" outright at Gate R-A | Step 9.5 | Stop orchestration. Branch + issue assignments remain (user reverts via git / `gh issue edit --remove-assignee` if desired). Researcher's `.team-notes/*.md` files remain for later reference. | -| User picks "Drop specific files" at Gate R-A | Step 9.5 | Recompute the batch composition against the reduced set. Regenerate the inline preview. Re-ask via `AskUserQuestion`. Branch name + issue assignments are NOT recomputed (already pushed / assigned); surface the mismatch in the final summary. | ## Parallelism caps (orchestrator self-enforced) -- N ≤ 6 files per batch (unchanged — bounds the per-agent context size, not - the live-agent count). -- Phase R: **1** agent (the researcher). -- **Phase R-A (step 9.5): no agents in flight.** The user-facing AskUserQuestion gate blocks all sub-agent spawning until the user approves. The researcher is idle but addressable for re-research dispatches. -- Phase IT: **2** agents in parallel (implementer ∥ tester), via one - orchestrator message containing two `Agent(...)` calls. -- Phase RV: **1** agent (the reviewer). -- Regression sweep dispatch (step 12): handled inside the still-running - tester via `SendMessage`. No extra spawn. -- **Total live-agent count: 4**, regardless of batch size. +- N ≤ 6 files per batch. +- Phase R: N parallel agents. +- Phase IT: 2N parallel agents (max 12 at N=6). +- Phase RV: N parallel agents. +- Regression sweep dispatch (step 11): batch parallel by touched fixture + filename; if more than 6 fixtures need expansion, serialise above 6. ## Notes for the orchestrator (you, the main agent) - The team-template role prompts at `.claude/team-templates/extension-impl.md` - are the **source of truth**. The batch-mode addendum inside each role's - prompt block is what activates one-team-for-all-files behaviour — it is - the single-file prompt PLUS the "Batch-mode operation" section, with - `{{BATCH_FILES}}` expanded. -- All paths in agent prompts must be repo-relative with forward slashes. -- Researcher is **mandatory** for the batch, even when individual files have - been seen before via `/implement-extensions`. One researcher produces all - N notes files; the implementer/tester/reviewer read them. -- Reviewer is **mandatory** for the batch — cheap insurance against subtle - OCL mistranslation across all N files. + are the **source of truth** for per-file behaviour. Substitute the + file-specific placeholders fresh for each agent spawn; do not let prompts + leak across files. +- All paths in agent prompts must be repo-relative with forward slashes (per + the convention used throughout the existing single-file command). +- Researcher is **mandatory** per file, even when the file has been seen before + via `/implement-extensions`. Researchers are cheap and produce the contract + the implementer/tester/reviewer read. +- Reviewers are **mandatory** per file — cheap insurance against subtle OCL + mistranslation. Even when the file is trivial spec-text-only. - The branch and the assignments persist even on partial failure. Be explicit in the final summary about which files succeeded vs which need follow-up. - Do NOT auto-commit. The user reviews `git diff` and commits manually. - If the user supplies a single file, route them to `/implement-extensions` with the same filename rather than creating a degenerate 1-file "batch" - team. -- **Prefer SendMessage over fresh Agent spawns inside the iteration loop.** - The named teammates retain their context (notes already read, prior - decisions, OCL chains they've already traced). A fresh `Agent(...)` call - for a single-file fix throws that context away and re-pays the prompt - cost. The only reason to spawn fresh is the "named teammate becomes - unresponsive" row in the failure-handling table. -- **Plan mode is handled by Gate 0 at the top of this file** — the orchestrator writes the proposed-execution plan to the plan file, calls `ExitPlanMode`, and proceeds on approval. The orchestrator never spawns sub-agents while plan mode is active, so the previous "degraded mode" workaround is no longer needed and has been removed. If for some reason plan mode re-engages mid-run (e.g. the user toggles it back on after Gate 0 approval), the orchestrator pauses any pending sub-agent spawns and surfaces the state to the user. -- **Gate R-A (step 9.5) is mandatory every run.** It is the only structural checkpoint between the researcher returning `spec ready` and the implementer + tester being spawned. Do not skip it even when the researcher reports zero ambiguities — the user explicitly asked for an unconditional gate so they can review per-method derivations before code is written. -- **Tool-level prompts (`Bash(...)`, `Edit(...)`, `Write(...)`, `TeamCreate`, `Agent(...)`) still surface per the user's `settings.json`.** Gate 0 and Gate R-A govern STRUCTURAL approval only. The user has explicitly chosen to keep handling tool-level prompts manually rather than auto-allowing them via permission rules or hooks. -- **Sub-agent-side deadlocks**: if an implementer / tester gets parked on a harness UI permission prompt the user cannot action (precedent: 2026-05-28 batch run for issues #111/#116/#118/#119/#130/#132), the orchestrator should take over the deadlocked work directly from its own permission scope (running `dotnet build` / `dotnet test` / `gh issue edit` itself). Send `shutdown_request` to the parked sub-agent. Surface clearly in the final summary. + branch. diff --git a/.claude/commands/implement-extensions.md b/.claude/commands/implement-extensions.md index 39deb76c..5aa0aa10 100644 --- a/.claude/commands/implement-extensions.md +++ b/.claude/commands/implement-extensions.md @@ -380,6 +380,16 @@ Report to the user: knows the implementation is grounded in spec prose rather than OCL. - **Issue checklist sync**: `` — `` newly ticked, `` newly added, `/` total (filled in after step 11). +- **Pre-filled commit message** (MANDATORY — append at the very end of the + final-summary message in a fenced code block, ready to copy): + + ``` + Fix # + ``` + + Where `` is the GitHub issue number handled by this run. Nothing else — + no body paragraphs, no per-method bullet list, no `Co-Authored-By` trailer, + no "🤖 Generated with …" footer. The single line is the entire message. Do NOT auto-commit. The user reviews and commits. diff --git a/.claude/team-templates/extension-impl.md b/.claude/team-templates/extension-impl.md index 5b7ba789..d8fa1a2d 100644 --- a/.claude/team-templates/extension-impl.md +++ b/.claude/team-templates/extension-impl.md @@ -53,22 +53,6 @@ Don't use this template when: | `{{METHOD_LIST}}` | Bullet list of methods to implement, in dependency order | (per-task) | | `{{ORCHESTRATOR_NAME}}` | Team-lead name for SendMessage | `team-lead` | -### Batch-mode placeholders (used by `/implement-extensions-batch`) - -When this template is instantiated by `/implement-extensions-batch`, each role -agent is spawned **once for the whole batch** instead of once per file. The -per-file slot values (`{{TARGET_INTERFACE}}`, `{{PRODUCTION_FILE}}`, -`{{TEST_FILE}}`, `{{NOTES_FILE}}`, `{{METHOD_LIST}}`, …) are replaced by a single -batch-wide placeholder: - -| Placeholder | Description | Example value | -|---|---|---| -| `{{BATCH_FILES}}` | Numbered list of files in the batch, each carrying the same per-file slot values listed above plus its individual `{{METHOD_LIST}}`. The agent walks the list sequentially in its single context. | `1. Foo — interface IFoo, prod SysML2.NET/Extend/FooExtensions.cs, tests …, notes .team-notes/foo-extensions-spec.md, methods […]\n2. Bar — …` | - -Shared placeholders (`{{REFERENCE_PRODUCTION_FILE}}`, `{{REFERENCE_TEST_FILE}}`, -`{{TEAM_NAME}}`, `{{ORCHESTRATOR_NAME}}`) are unchanged — they are the same -across every file in the batch. - ## Workflow ``` @@ -79,12 +63,6 @@ across every file in the batch. in the recent TypeExtensions task) and surfaces transitive stub-blockers before the implementer hits them. -1.5 Orchestrator → renders inline per-file spec preview, asks user via - AskUserQuestion to approve before any code is written. - MANDATORY every run, even when researcher reports zero - ambiguities. See "Phase R-A" / step 5.5 in - .claude/commands/implement-extensions.md. - 2. Implementer → implements all methods in {{PRODUCTION_FILE}}, sliced by dependency tier (Tier 1: direct OfType filters; Tier 2: chains over Tier 1; Tier 3: depends on operations; Tier 4: @@ -102,72 +80,30 @@ across every file in the batch. fixtures that now fail get updated as part of the same PR. ``` -### Batch-mode workflow (one team for all N files) - -When invoked through `/implement-extensions-batch`, the workflow above runs -**once for the whole batch** instead of once per file: - -``` -1. Researcher → walks {{BATCH_FILES}} and writes ONE - .team-notes/-extensions-spec.md per file IN ITS SINGLE - AGENT RUN. Reads {{REFERENCE_PRODUCTION_FILE}} and - {{REFERENCE_TEST_FILE}} ONCE up front, not per file. - -1.5 Orchestrator → renders inline per-file spec preview (one block per - batch file), asks user via AskUserQuestion to approve - before any code is written. Options: Approve / Drop - specific files / Abort + research again. MANDATORY every - run, even when researcher reports zero ambiguities. See - "Phase R-A" / step 9.5 in - .claude/commands/implement-extensions-batch.md. - -2. Implementer → walks {{BATCH_FILES}} and implements each - SysML2.NET/Extend/Extensions.cs in its single agent run. - Reads each file's notes file before editing it. ACL = - the set of production files listed in {{BATCH_FILES}}; - refuses every other path. - -3. Tester → walks {{BATCH_FILES}} and rewrites each - SysML2.NET.Tests/Extend/ExtensionsTestFixture.cs. ACL = - the set of test fixtures listed in {{BATCH_FILES}}, PLUS - (after the orchestrator's regression-sweep SendMessage) - any sibling *ExtensionsTestFixture.cs touched by the new - implementations. - -4. Reviewer → walks {{BATCH_FILES}} and applies the OCL-translation + - test-fixture checklists per file; READ-ONLY. - -5. Orchestrator → after Phases R / R-A / IT, runs ONE consolidated build, - ONE consolidated targeted test (filter joining every - fixture in the batch), then the regression sweep. On any - failure, it sends a SendMessage to the relevant named - teammate (`researcher`, `implementer`, or `tester`) with - the failing-file list — the same agent keeps its context - and fixes in place. Fresh Agent spawns are not used - inside the iteration loop. -``` - -The role boundaries are **identical** to single-file mode: the implementer -never edits test files, the tester never edits production files, the reviewer -is read-only. The only thing that changes is the size of the per-role allowed -file set (one file → N files in the batch). +## Plan-mode-aware prompting (added 2026-05-28) -## Plan-mode-aware prompting (reworked 2026-05-28) +If the orchestrator session is in plan mode when this template is used, sub-agents +will inherit it and cannot apply edits. The Agent-tool `mode: "acceptEdits"` +parameter does NOT override the inherited state on the current Claude Code build. -Plan mode is now the natural pre-execution approval gate for both `/implement-extensions` and `/implement-extensions-batch` — the orchestrator stays in plan mode, does read-only pre-flight, writes the proposed-execution plan to the plan file, calls `ExitPlanMode`, and proceeds on approval. The "degraded mode" workaround documented in the previous version of this section (orchestrator applies edits on behalf of sub-agents) is no longer needed because the orchestrator never spawns sub-agents while plan mode is active. +Role prompts in this template are written so that the orchestrator can fall back +to applying edits itself when this happens: -See the **"Pre-flight: plan mode IS the pre-execution approval gate (Gate 0)"** section in each command file: -- `.claude/commands/implement-extensions.md` -- `.claude/commands/implement-extensions-batch.md` +- **Researcher** prompts always direct the agent to write the spec to its + declared `{{NOTES_FILE}}` location. In plan-mode-degraded runs the agent will + write to a per-agent plan file under + `C:\Users\\.claude\plans\-agent-.md` instead; the + orchestrator then copies it to `.team-notes/`. +- **Implementer / tester** prompts must emit the verbatim production / test code + in their text response (or, equivalently, in their per-agent plan file) so it + survives a plan-mode block. The orchestrator extracts and applies it via + `Edit` / `Write`. +- **Reviewer** prompts are already read-only and unaffected by plan mode. -The structural approval workflow ships with **two gates** now: - -| Gate | Fires | Approves | UI | -|---|---|---|---| -| Gate 0 (pre-execution) | At invocation, if orchestrator is in plan mode | Composition + branch + team + model picks | `ExitPlanMode` plan-approval UI | -| Gate R-A (post-researcher) | After researcher returns `spec ready`, before implementer/tester spawn | The per-method derivation plan inline-rendered from each `.team-notes/-extensions-spec.md` | `AskUserQuestion` with 2–3 options | - -Tool-level permission prompts (`Bash(dotnet build *)`, `Bash(git push *)`, `TeamCreate`, `Agent(...)`, etc.) continue to surface per the user's `settings.json`. The gates govern STRUCTURAL approval only; they do not auto-allow individual tool calls. +The `/implement-extensions-batch` command body documents the degraded-mode flow +end-to-end in its "Pre-flight: detect orchestrator plan mode" section. The +single-file `/implement-extensions` command should also adopt the same flow — +see that file's notes section. ## Hard scope-discipline rule (v2) @@ -379,33 +315,6 @@ Send a SendMessage to `{{ORCHESTRATOR_NAME}}` with summary `spec ready` and a li of methods where the OCL is ambiguous, missing, or transitively depends on other stubbed extensions. -## Batch-mode operation - -When the orchestrator passes `{{BATCH_FILES}}` instead of a single -`{{NOTES_FILE}}` / `{{PRODUCTION_FILE}}` set, you are the SOLE researcher for -the whole batch. Operate as follows: - -1. **Read shared context ONCE** at the start of your run, not per file: - - `{{REFERENCE_PRODUCTION_FILE}}` (the canonical NamespaceExtensions.cs). - - The OCL operator translation idioms in the team template (already in your - prompt). - The XMI files (`Resources/KerML_only_xmi.uml`, - `Resources/SysML_only_xmi.uml`) and the spec text files are also shared — - keep a single open mental map across files; do not re-summarize each. -2. **Iterate `{{BATCH_FILES}}` sequentially.** For each entry — which carries - its own `{{TARGET_INTERFACE}}`, `{{TARGET_METACLASS_NAME}}`, - `{{SUBJECT_PARAM}}`, `{{PRODUCTION_FILE}}`, `{{NOTES_FILE}}`, and - `{{METHOD_LIST}}` — perform your single-file research workflow above and - write that entry's `{{NOTES_FILE}}`. -3. **Edit ACL = the set of `{{NOTES_FILE}}` paths listed in `{{BATCH_FILES}}`.** - Every other path is read-only. If a tool call would violate this, abort and - message `{{ORCHESTRATOR_NAME}}`. -4. **Final SendMessage** to `{{ORCHESTRATOR_NAME}}`: - `spec ready, files: N, ambiguous-OCL: {Foo: [methods], …}, - spec-text-only: {Foo: [methods], …}, stub-blocker-flagged-in: {Foo: [methods], …}`. -5. **Remain addressable** for the rest of the run. The implementer, tester, or - reviewer may SendMessage you a clarification request on one file's OCL. - Begin. ``` @@ -492,45 +401,6 @@ Send a SendMessage to `{{ORCHESTRATOR_NAME}}` with: Begin by reading `{{NOTES_FILE}}`, then the method `` blocks in {{PRODUCTION_FILE}}, then the reference template ({{REFERENCE_PRODUCTION_FILE}}), then making the edits. - -## Batch-mode operation - -When the orchestrator passes `{{BATCH_FILES}}` instead of a single -`{{PRODUCTION_FILE}}` / `{{NOTES_FILE}}` set, you are the SOLE implementer for -the whole batch. Operate as follows: - -1. **Read shared context ONCE** at the start of your run: - - `{{REFERENCE_PRODUCTION_FILE}}` (the canonical NamespaceExtensions.cs). - - The OCL operator translation table is already in your prompt. -2. **Edit ACL = the set of `{{PRODUCTION_FILE}}` paths listed in - `{{BATCH_FILES}}`.** You may Edit/Write ONLY those production files. - - Refuse every test fixture path (tester's territory). - - Refuse every other production file in `SysML2.NET/Extend/` or - `SysML2.NET/Core/` (scope-discipline rule). - - Refuse auto-generated POCOs and code-gen templates. - If a tool call would violate this, abort and message `{{ORCHESTRATOR_NAME}}`. -3. **Iterate `{{BATCH_FILES}}` in a sensible order.** Independent metaclasses - first (no cross-file references), files whose OCL transitively reads a - sibling-batch metaclass last. For each entry — which carries its own - `{{TARGET_INTERFACE}}`, `{{SUBJECT_PARAM}}`, `{{PRODUCTION_FILE}}`, - `{{NOTES_FILE}}`, and `{{METHOD_LIST}}` — read that entry's `{{NOTES_FILE}}` - first, then implement each method in its `{{PRODUCTION_FILE}}` per the - single-file workflow above. -4. **Build once at the end**: - ```bash - dotnet build SysML2.NET/SysML2.NET.csproj - ``` - Not per file — the build is shared. On failure, identify which file's - diff broke it and iterate. -5. **Final SendMessage** to `{{ORCHESTRATOR_NAME}}`: - `dev complete, files: N, order: [Foo, Bar, …], - deviations-per-file: {Foo: [...], Bar: [...]}, - upstream-stubs-touched: [methods you couldn't fully implement and how you - handled them]`. -6. **Remain addressable.** When the orchestrator's targeted-test run surfaces - an OCL mistranslation in any file's production, it will SendMessage you - the `(file, method, observed-vs-expected)` triple. Fix in place — your - context (notes already read, prior decisions) is preserved. ``` --- @@ -642,50 +512,6 @@ Send a SendMessage to `{{ORCHESTRATOR_NAME}}` with: Begin by reading `{{NOTES_FILE}}` (each method has a "Test plan" section), the production methods you need to test, the reference fixture (`{{REFERENCE_TEST_FILE}}`), the current `{{TEST_FILE}}`, then making the edits. - -## Batch-mode operation - -When the orchestrator passes `{{BATCH_FILES}}` instead of a single -`{{TEST_FILE}}` / `{{NOTES_FILE}}` set, you are the SOLE tester for the whole -batch. Operate as follows: - -1. **Read shared context ONCE** at the start of your run: - - `{{REFERENCE_TEST_FILE}}` (the canonical NamespaceExtensionsTestFixture.cs). - - NUnit conventions in `TESTING.md` at the repo root. -2. **Edit ACL = the set of `{{TEST_FILE}}` paths listed in `{{BATCH_FILES}}`.** - - Refuse every production file under `SysML2.NET/Extend/` or - `SysML2.NET/Core/` (implementer's territory and the scope-discipline rule). - - Refuse auto-generated POCOs and code-gen templates. - - **Regression-sweep extension**: once the orchestrator sends you the - regression-sweep brief (after the full-solution `dotnet test`), the ACL - extends to the listed sibling `*ExtensionsTestFixture.cs` files for that - dispatch only. - If a tool call would violate the current ACL, abort and message - `{{ORCHESTRATOR_NAME}}`. -3. **Iterate `{{BATCH_FILES}}` sequentially.** For each entry — which carries - its own `{{TARGET_INTERFACE}}`, `{{TARGET_METACLASS_NAME}}`, - `{{TEST_FILE}}`, `{{NOTES_FILE}}`, and `{{METHOD_LIST}}` — read that - entry's `{{NOTES_FILE}}` (the "Test plan" sections) and rewrite its - `{{TEST_FILE}}` per the single-file workflow above. -4. **Parallel-mode caveat is unchanged**: while you are spawned in parallel - with the implementer, you run `dotnet build` on the test project ONLY and - MUST NOT run `dotnet test`. The orchestrator runs the consolidated targeted - test after both of you return. -5. **Final SendMessage** to `{{ORCHESTRATOR_NAME}}`: - `tests complete, files: N, - stub-blocker-pattern-applied-in: {Foo: [methods], …}, - weak-populated-case-in: {Foo: [methods, reason], …}`. -6. **Remain addressable** for two follow-up dispatches: - - **Wrong assertion fix**: if the orchestrator's targeted-test run blames - a failing assertion on the test code rather than the production OCL, - it will SendMessage you the `(file, method, observed-vs-expected)` - triple. Fix in place. - - **Regression sweep**: after Phase V the orchestrator sends you the list - of sibling fixtures whose `Throws.TypeOf()` - assertions now fail. Apply the **expand-don't-replace** edit pattern - (filter discrimination + predicate completeness + owned vs inherited + - null-projection guard) per sibling fixture. The ACL extension above - applies for this dispatch. ``` --- @@ -802,27 +628,6 @@ Produce a concise report to `{{ORCHESTRATOR_NAME}}` (via SendMessage) with: Do NOT modify any code yourself. The orchestrator (or the implementer/tester re-dispatched by the orchestrator) will action your findings. - -## Batch-mode operation - -When the orchestrator passes `{{BATCH_FILES}}` instead of a single -`(notes, production, tests)` triple, you are the SOLE reviewer for the whole -batch. Operate as follows: - -1. **Edit ACL = none.** You are READ-ONLY across the entire repo — same as - single-file mode. The size of `{{BATCH_FILES}}` does not loosen this. -2. **Iterate `{{BATCH_FILES}}` sequentially.** For each entry, walk its - `(notes file, production file, test file)` triple and apply the full - single-file OCL-translation checklist + test-fixture checklist above. -3. **Final SendMessage** to `{{ORCHESTRATOR_NAME}}`: - - Top line: `OK` or `NEEDS FIX` (batch-wide verdict; NEEDS FIX wins if any - file fails). - - Body: per-file findings grouped as - `{Foo: [{file, line, concern, suggested fix}, …], - Bar: [...], …}`. - For files with no findings, list `Foo: OK`. -4. The orchestrator routes individual findings to `implementer` or `tester` - via SendMessage; you do not edit and you do not re-run. ``` --- @@ -852,24 +657,6 @@ In a fresh conversation, when the user asks to implement methods in another For low-friction invocation, use the slash command at `.claude/commands/implement-extensions.md` which automates steps 1–7. -### Instantiation for a batch (one team for N files) - -When `/implement-extensions-batch` invokes this template, the per-role prompts -are augmented with the "Batch-mode operation" section already documented inside -each role prompt above: - -1. `TeamCreate({team_name: "batch-extensions-impl-"})` once. -2. Spawn FOUR named teammates (`researcher`, `implementer`, `tester`, - `reviewer`) via `Agent` calls, each with the role's full prompt (single-file - body + Batch-mode operation addendum) and `{{BATCH_FILES}}` expanded. -3. Sequence: researcher → (implementer ∥ tester) → orchestrator verification - → regression-sweep brief via `SendMessage to: "tester"` → reviewer. -4. Iterate fixes via `SendMessage` to the named teammate — do not spawn fresh - `Agent` calls inside the iteration loop. - -See `.claude/commands/implement-extensions-batch.md` for the end-to-end -orchestration. - ## Provenance v2 distilled from the `type-extensions-impl` task (33 methods in diff --git a/SysML2.NET.Tests/Extend/AnalysisCaseDefinitionExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/AnalysisCaseDefinitionExtensionsTestFixture.cs index 9d76d1b7..47a3b89c 100644 --- a/SysML2.NET.Tests/Extend/AnalysisCaseDefinitionExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/AnalysisCaseDefinitionExtensionsTestFixture.cs @@ -1,38 +1,52 @@ -// ------------------------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------- // -// +// // Copyright 2022-2026 Starion Group S.A. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// +// // // ------------------------------------------------------------------------------------------------ namespace SysML2.NET.Tests.Extend { using System; - + using NUnit.Framework; - + + using SysML2.NET.Core.POCO.Kernel.Functions; using SysML2.NET.Core.POCO.Systems.AnalysisCases; + using SysML2.NET.Extensions; [TestFixture] public class AnalysisCaseDefinitionExtensionsTestFixture { [Test] - public void ComputeResultExpression_ThrowsNotSupportedException() + public void VerifyComputeResultExpression() { - Assert.That(() => ((IAnalysisCaseDefinition)null).ComputeResultExpression(), Throws.TypeOf()); + Assert.That(() => ((IAnalysisCaseDefinition)null).ComputeResultExpression(), Throws.TypeOf()); + + var analysisCaseDefinition = new AnalysisCaseDefinition(); + + // Empty case: no ResultExpressionMembership in featureMembership → null. + Assert.That(analysisCaseDefinition.ComputeResultExpression(), Is.Null); + + // Populated case: ResultExpressionMembership owns an Expression → returns the Expression. + var expression = new Expression(); + var resultExpressionMembership = new ResultExpressionMembership(); + analysisCaseDefinition.AssignOwnership(resultExpressionMembership, expression); + + Assert.That(analysisCaseDefinition.ComputeResultExpression(), Is.SameAs(expression)); } } } diff --git a/SysML2.NET.Tests/Extend/AnalysisCaseUsageExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/AnalysisCaseUsageExtensionsTestFixture.cs index 0bdcbd9d..3dc33d04 100644 --- a/SysML2.NET.Tests/Extend/AnalysisCaseUsageExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/AnalysisCaseUsageExtensionsTestFixture.cs @@ -1,44 +1,78 @@ -// ------------------------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------- // -// +// // Copyright 2022-2026 Starion Group S.A. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// +// // // ------------------------------------------------------------------------------------------------ namespace SysML2.NET.Tests.Extend { using System; - + using NUnit.Framework; - + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Kernel.Functions; using SysML2.NET.Core.POCO.Systems.AnalysisCases; + using SysML2.NET.Core.POCO.Systems.DefinitionAndUsage; + using SysML2.NET.Extensions; [TestFixture] public class AnalysisCaseUsageExtensionsTestFixture { [Test] - public void ComputeAnalysisCaseDefinition_ThrowsNotSupportedException() + public void VerifyComputeAnalysisCaseDefinition() { - Assert.That(() => ((IAnalysisCaseUsage)null).ComputeAnalysisCaseDefinition(), Throws.TypeOf()); + Assert.That(() => ((IAnalysisCaseUsage)null).ComputeAnalysisCaseDefinition(), Throws.TypeOf()); + + var analysisCaseUsage = new AnalysisCaseUsage(); + + // Empty case: no FeatureTyping whose Type is an IAnalysisCaseDefinition → null. + Assert.That(analysisCaseUsage.ComputeAnalysisCaseDefinition(), Is.Null); + + // Negative case: FeatureTyping whose Type is a Usage (not IAnalysisCaseDefinition) — no match → null. + var nonDefinitionTyping = new FeatureTyping { Type = new Usage() }; + analysisCaseUsage.AssignOwnership(nonDefinitionTyping); + + Assert.That(analysisCaseUsage.ComputeAnalysisCaseDefinition(), Is.Null); + + // Populated case: FeatureTyping whose Type is an AnalysisCaseDefinition → returns the AnalysisCaseDefinition. + var analysisCaseDefinition = new AnalysisCaseDefinition(); + var analysisCaseDefinitionTyping = new FeatureTyping { Type = analysisCaseDefinition }; + analysisCaseUsage.AssignOwnership(analysisCaseDefinitionTyping); + + Assert.That(analysisCaseUsage.ComputeAnalysisCaseDefinition(), Is.SameAs(analysisCaseDefinition)); } - + [Test] - public void ComputeResultExpression_ThrowsNotSupportedException() + public void VerifyComputeResultExpression() { - Assert.That(() => ((IAnalysisCaseUsage)null).ComputeResultExpression(), Throws.TypeOf()); + Assert.That(() => ((IAnalysisCaseUsage)null).ComputeResultExpression(), Throws.TypeOf()); + + var analysisCaseUsage = new AnalysisCaseUsage(); + + // Empty case: no ResultExpressionMembership in featureMembership → null. + Assert.That(analysisCaseUsage.ComputeResultExpression(), Is.Null); + + // Populated case: ResultExpressionMembership owns an Expression → returns the Expression. + var expression = new Expression(); + var resultExpressionMembership = new ResultExpressionMembership(); + analysisCaseUsage.AssignOwnership(resultExpressionMembership, expression); + + Assert.That(analysisCaseUsage.ComputeResultExpression(), Is.SameAs(expression)); } } } diff --git a/SysML2.NET.Tests/Extend/CaseDefinitionExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/CaseDefinitionExtensionsTestFixture.cs index 0e053d80..5745038d 100644 --- a/SysML2.NET.Tests/Extend/CaseDefinitionExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/CaseDefinitionExtensionsTestFixture.cs @@ -1,50 +1,112 @@ // ------------------------------------------------------------------------------------------------- // -// +// // Copyright 2022-2026 Starion Group S.A. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// +// // // ------------------------------------------------------------------------------------------------ namespace SysML2.NET.Tests.Extend { using System; - + using NUnit.Framework; - using SysML2.NET.Core.POCO.Systems.Cases ; + using SysML2.NET.Core.POCO.Kernel.Behaviors; + using SysML2.NET.Core.POCO.Systems.Cases; + using SysML2.NET.Core.POCO.Systems.Parts; + using SysML2.NET.Core.POCO.Systems.Requirements; + using SysML2.NET.Core.POCO.Systems.DefinitionAndUsage; + using SysML2.NET.Extensions; [TestFixture] public class CaseDefinitionExtensionsTestFixture { [Test] - public void ComputeActorParameter_ThrowsNotSupportedException() + public void VerifyComputeActorParameter() { - Assert.That(() => ((ICaseDefinition)null).ComputeActorParameter(), Throws.TypeOf()); + Assert.That(() => ((ICaseDefinition)null).ComputeActorParameter(), Throws.TypeOf()); + + var caseDefinition = new CaseDefinition(); + + Assert.That(caseDefinition.ComputeActorParameter(), Is.Empty); + + // Discrimination: add a ParameterMembership (not ActorMembership) — must be excluded from result. + var parameterMembership = new ParameterMembership(); + var parameterUsage = new Usage(); + caseDefinition.AssignOwnership(parameterMembership, parameterUsage); + + Assert.That(caseDefinition.ComputeActorParameter(), Is.Empty); + + // For Later: populated case depends on IActorMembership.ComputeOwnedActorParameter, which is still a stub. + var actorMembership = new ActorMembership(); + var actorPartUsage = new PartUsage(); + caseDefinition.AssignOwnership(actorMembership, actorPartUsage); + + Assert.That(() => caseDefinition.ComputeActorParameter(), Throws.TypeOf()); } - + [Test] - public void ComputeObjectiveRequirement_ThrowsNotSupportedException() + public void VerifyComputeObjectiveRequirement() { - Assert.That(() => ((ICaseDefinition)null).ComputeObjectiveRequirement(), Throws.TypeOf()); + Assert.That(() => ((ICaseDefinition)null).ComputeObjectiveRequirement(), Throws.TypeOf()); + + var caseDefinition = new CaseDefinition(); + + // Empty case: no ObjectiveMembership in featureMembership → null. + Assert.That(caseDefinition.ComputeObjectiveRequirement(), Is.Null); + + // Discrimination: add a ParameterMembership (not ObjectiveMembership) — still null. + var parameterMembership = new ParameterMembership(); + var parameterUsage = new Usage(); + caseDefinition.AssignOwnership(parameterMembership, parameterUsage); + + Assert.That(caseDefinition.ComputeObjectiveRequirement(), Is.Null); + + // Populated case: ObjectiveMembership owns a RequirementUsage → returns the RequirementUsage. + var objectiveMembership = new ObjectiveMembership(); + var requirementUsage = new RequirementUsage(); + caseDefinition.AssignOwnership(objectiveMembership, requirementUsage); + + Assert.That(caseDefinition.ComputeObjectiveRequirement(), Is.SameAs(requirementUsage)); } - + [Test] - public void ComputeSubjectParameter_ThrowsNotSupportedException() + public void VerifyComputeSubjectParameter() { - Assert.That(() => ((ICaseDefinition)null).ComputeSubjectParameter(), Throws.TypeOf()); + Assert.That(() => ((ICaseDefinition)null).ComputeSubjectParameter(), Throws.TypeOf()); + + var caseDefinition = new CaseDefinition(); + + // Empty case: no SubjectMembership in featureMembership → null. + Assert.That(caseDefinition.ComputeSubjectParameter(), Is.Null); + + // Discrimination: add a ParameterMembership (not SubjectMembership) → still null. + var parameterMembership = new ParameterMembership(); + var parameterUsage = new Usage(); + caseDefinition.AssignOwnership(parameterMembership, parameterUsage); + + Assert.That(caseDefinition.ComputeSubjectParameter(), Is.Null); + + // Populated case: SubjectMembership is present alongside the earlier ParameterMembership. + // OfType must discriminate — only the subject's ownedSubjectParameter surfaces. + var subjectMembership = new SubjectMembership(); + var subjectUsage = new Usage(); + caseDefinition.AssignOwnership(subjectMembership, subjectUsage); + + Assert.That(caseDefinition.ComputeSubjectParameter(), Is.SameAs(subjectUsage)); } } } diff --git a/SysML2.NET.Tests/Extend/CaseUsageExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/CaseUsageExtensionsTestFixture.cs index a892228a..7cf27a63 100644 --- a/SysML2.NET.Tests/Extend/CaseUsageExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/CaseUsageExtensionsTestFixture.cs @@ -1,56 +1,137 @@ // ------------------------------------------------------------------------------------------------- // -// +// // Copyright 2022-2026 Starion Group S.A. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// +// // // ------------------------------------------------------------------------------------------------ namespace SysML2.NET.Tests.Extend { using System; - + using NUnit.Framework; - + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Kernel.Behaviors; using SysML2.NET.Core.POCO.Systems.Cases; + using SysML2.NET.Core.POCO.Systems.Parts; + using SysML2.NET.Core.POCO.Systems.Requirements; + using SysML2.NET.Core.POCO.Systems.DefinitionAndUsage; + using SysML2.NET.Extensions; [TestFixture] public class CaseUsageExtensionsTestFixture { [Test] - public void ComputeActorParameter_ThrowsNotSupportedException() + public void VerifyComputeActorParameter() { - Assert.That(() => ((ICaseUsage)null).ComputeActorParameter(), Throws.TypeOf()); + Assert.That(() => ((ICaseUsage)null).ComputeActorParameter(), Throws.TypeOf()); + + var caseUsage = new CaseUsage(); + + Assert.That(caseUsage.ComputeActorParameter(), Is.Empty); + + // Discrimination: add a ParameterMembership (not ActorMembership) — must be excluded from result. + var parameterMembership = new ParameterMembership(); + var parameterUsage = new Usage(); + caseUsage.AssignOwnership(parameterMembership, parameterUsage); + + Assert.That(caseUsage.ComputeActorParameter(), Is.Empty); + + // For Later: populated case depends on IActorMembership.ComputeOwnedActorParameter, which is still a stub. + var actorMembership = new ActorMembership(); + var actorPartUsage = new PartUsage(); + caseUsage.AssignOwnership(actorMembership, actorPartUsage); + + Assert.That(() => caseUsage.ComputeActorParameter(), Throws.TypeOf()); } - + [Test] - public void ComputeCaseDefinition_ThrowsNotSupportedException() + public void VerifyComputeCaseDefinition() { - Assert.That(() => ((ICaseUsage)null).ComputeCaseDefinition(), Throws.TypeOf()); + Assert.That(() => ((ICaseUsage)null).ComputeCaseDefinition(), Throws.TypeOf()); + + var caseUsage = new CaseUsage(); + + // Empty case: no FeatureTyping whose Type is an ICaseDefinition → null. + Assert.That(caseUsage.ComputeCaseDefinition(), Is.Null); + + // Negative case: FeatureTyping whose Type is a Usage (not ICaseDefinition) — no match → null. + var nonDefinitionTyping = new FeatureTyping { Type = new Usage() }; + caseUsage.AssignOwnership(nonDefinitionTyping); + + Assert.That(caseUsage.ComputeCaseDefinition(), Is.Null); + + // Populated case: FeatureTyping whose Type is a CaseDefinition → returns the CaseDefinition. + var caseDefinition = new CaseDefinition(); + var caseDefinitionTyping = new FeatureTyping { Type = caseDefinition }; + caseUsage.AssignOwnership(caseDefinitionTyping); + + Assert.That(caseUsage.ComputeCaseDefinition(), Is.SameAs(caseDefinition)); } - + [Test] - public void ComputeObjectiveRequirement_ThrowsNotSupportedException() + public void VerifyComputeObjectiveRequirement() { - Assert.That(() => ((ICaseUsage)null).ComputeObjectiveRequirement(), Throws.TypeOf()); + Assert.That(() => ((ICaseUsage)null).ComputeObjectiveRequirement(), Throws.TypeOf()); + + var caseUsage = new CaseUsage(); + + // Empty case: no ObjectiveMembership in featureMembership → null. + Assert.That(caseUsage.ComputeObjectiveRequirement(), Is.Null); + + // Discrimination: add a ParameterMembership (not ObjectiveMembership) — still null. + var parameterMembership = new ParameterMembership(); + var parameterUsage = new Usage(); + caseUsage.AssignOwnership(parameterMembership, parameterUsage); + + Assert.That(caseUsage.ComputeObjectiveRequirement(), Is.Null); + + // Populated case: ObjectiveMembership owns a RequirementUsage → returns the RequirementUsage. + var objectiveMembership = new ObjectiveMembership(); + var requirementUsage = new RequirementUsage(); + caseUsage.AssignOwnership(objectiveMembership, requirementUsage); + + Assert.That(caseUsage.ComputeObjectiveRequirement(), Is.SameAs(requirementUsage)); } - + [Test] - public void ComputeSubjectParameter_ThrowsNotSupportedException() + public void VerifyComputeSubjectParameter() { - Assert.That(() => ((ICaseUsage)null).ComputeSubjectParameter(), Throws.TypeOf()); + Assert.That(() => ((ICaseUsage)null).ComputeSubjectParameter(), Throws.TypeOf()); + + var caseUsage = new CaseUsage(); + + // Empty case: no SubjectMembership in featureMembership → null. + Assert.That(caseUsage.ComputeSubjectParameter(), Is.Null); + + // Discrimination: add a ParameterMembership (not SubjectMembership) → still null. + var parameterMembership = new ParameterMembership(); + var parameterUsage = new Usage(); + caseUsage.AssignOwnership(parameterMembership, parameterUsage); + + Assert.That(caseUsage.ComputeSubjectParameter(), Is.Null); + + // Populated case: SubjectMembership is present alongside the earlier ParameterMembership. + // OfType must discriminate — only the subject's ownedSubjectParameter surfaces. + var subjectMembership = new SubjectMembership(); + var subjectUsage = new Usage(); + caseUsage.AssignOwnership(subjectMembership, subjectUsage); + + Assert.That(caseUsage.ComputeSubjectParameter(), Is.SameAs(subjectUsage)); } } } diff --git a/SysML2.NET.Tests/Extend/VerificationCaseDefinitionExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/VerificationCaseDefinitionExtensionsTestFixture.cs index 3a03998c..aaf5996a 100644 --- a/SysML2.NET.Tests/Extend/VerificationCaseDefinitionExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/VerificationCaseDefinitionExtensionsTestFixture.cs @@ -1,38 +1,61 @@ // ------------------------------------------------------------------------------------------------- // -// +// // Copyright 2022-2026 Starion Group S.A. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// +// // // ------------------------------------------------------------------------------------------------ namespace SysML2.NET.Tests.Extend { using System; - + using NUnit.Framework; - + + using SysML2.NET.Core.POCO.Systems.Cases; + using SysML2.NET.Core.POCO.Systems.Requirements; using SysML2.NET.Core.POCO.Systems.VerificationCases; + using SysML2.NET.Extensions; [TestFixture] public class VerificationCaseDefinitionExtensionsTestFixture { [Test] - public void ComputeVerifiedRequirement_ThrowsNotSupportedException() + public void VerifyComputeVerifiedRequirement() { - Assert.That(() => ((IVerificationCaseDefinition)null).ComputeVerifiedRequirement(), Throws.TypeOf()); + Assert.That(() => ((IVerificationCaseDefinition)null).ComputeVerifiedRequirement(), Throws.TypeOf()); + + var verificationCaseDefinition = new VerificationCaseDefinition(); + + // Empty case A: objectiveRequirement is null (no ObjectiveMembership) → empty list. + Assert.That(verificationCaseDefinition.ComputeVerifiedRequirement(), Is.Empty); + + // Empty case B: objectiveRequirement is set but its featureMembership has no + // RequirementVerificationMembership → empty list. + var objectiveMembership = new ObjectiveMembership(); + var requirementUsage = new RequirementUsage(); + verificationCaseDefinition.AssignOwnership(objectiveMembership, requirementUsage); + + Assert.That(verificationCaseDefinition.ComputeVerifiedRequirement(), Is.Empty); + + // For Later: populated case depends on IRequirementVerificationMembership.ComputeVerifiedRequirement, which is still a stub. + var requirementVerificationMembership = new RequirementVerificationMembership(); + var verifiedRequirement = new RequirementUsage(); + requirementUsage.AssignOwnership(requirementVerificationMembership, verifiedRequirement); + + Assert.That(() => verificationCaseDefinition.ComputeVerifiedRequirement(), Throws.TypeOf()); } } } diff --git a/SysML2.NET.Tests/Extend/VerificationCaseUsageExtensionsTestFixture.cs b/SysML2.NET.Tests/Extend/VerificationCaseUsageExtensionsTestFixture.cs index 4686aa12..a7ac9b28 100644 --- a/SysML2.NET.Tests/Extend/VerificationCaseUsageExtensionsTestFixture.cs +++ b/SysML2.NET.Tests/Extend/VerificationCaseUsageExtensionsTestFixture.cs @@ -1,43 +1,87 @@ // ------------------------------------------------------------------------------------------------- // -// +// // Copyright 2022-2026 Starion Group S.A. -// +// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at -// +// // http://www.apache.org/licenses/LICENSE-2.0 -// +// // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// +// // // ------------------------------------------------------------------------------------------------ namespace SysML2.NET.Tests.Extend { using System; - + using NUnit.Framework; - + + using SysML2.NET.Core.POCO.Core.Features; + using SysML2.NET.Core.POCO.Systems.Cases; + using SysML2.NET.Core.POCO.Systems.DefinitionAndUsage; + using SysML2.NET.Core.POCO.Systems.Requirements; using SysML2.NET.Core.POCO.Systems.VerificationCases; + using SysML2.NET.Extensions; [TestFixture] public class VerificationCaseUsageExtensionsTestFixture { [Test] - public void ComputeVerificationCaseDefinition_ThrowsNotSupportedException() + public void VerifyComputeVerificationCaseDefinition() { - Assert.That(() => ((IVerificationCaseUsage)null).ComputeVerificationCaseDefinition(), Throws.TypeOf()); + Assert.That(() => ((IVerificationCaseUsage)null).ComputeVerificationCaseDefinition(), Throws.TypeOf()); + + var verificationCaseUsage = new VerificationCaseUsage(); + + // Empty case: no FeatureTyping whose Type is an IVerificationCaseDefinition → null. + Assert.That(verificationCaseUsage.ComputeVerificationCaseDefinition(), Is.Null); + + // Negative case: FeatureTyping whose Type is a Usage (not IVerificationCaseDefinition) — no match → null. + var nonDefinitionTyping = new FeatureTyping { Type = new Usage() }; + verificationCaseUsage.AssignOwnership(nonDefinitionTyping); + + Assert.That(verificationCaseUsage.ComputeVerificationCaseDefinition(), Is.Null); + + // Populated case: FeatureTyping whose Type is a VerificationCaseDefinition → returns the VerificationCaseDefinition. + var verificationCaseDefinition = new VerificationCaseDefinition(); + var verificationCaseDefinitionTyping = new FeatureTyping { Type = verificationCaseDefinition }; + verificationCaseUsage.AssignOwnership(verificationCaseDefinitionTyping); + + Assert.That(verificationCaseUsage.ComputeVerificationCaseDefinition(), Is.SameAs(verificationCaseDefinition)); } + [Test] - public void ComputeVerifiedRequirement_ThrowsNotSupportedException() + public void VerifyComputeVerifiedRequirement() { - Assert.That(() => ((IVerificationCaseUsage)null).ComputeVerifiedRequirement(), Throws.TypeOf()); + Assert.That(() => ((IVerificationCaseUsage)null).ComputeVerifiedRequirement(), Throws.TypeOf()); + + var verificationCaseUsage = new VerificationCaseUsage(); + + // Empty case A: objectiveRequirement is null (no ObjectiveMembership) → empty list. + Assert.That(verificationCaseUsage.ComputeVerifiedRequirement(), Is.Empty); + + // Empty case B: objectiveRequirement is set but its featureMembership has no + // RequirementVerificationMembership → empty list. + var objectiveMembership = new ObjectiveMembership(); + var requirementUsage = new RequirementUsage(); + verificationCaseUsage.AssignOwnership(objectiveMembership, requirementUsage); + + Assert.That(verificationCaseUsage.ComputeVerifiedRequirement(), Is.Empty); + + // For Later: populated case depends on IRequirementVerificationMembership.ComputeVerifiedRequirement, which is still a stub. + var requirementVerificationMembership = new RequirementVerificationMembership(); + var verifiedRequirement = new RequirementUsage(); + requirementUsage.AssignOwnership(requirementVerificationMembership, verifiedRequirement); + + Assert.That(() => verificationCaseUsage.ComputeVerifiedRequirement(), Throws.TypeOf()); } } } diff --git a/SysML2.NET/Extend/AnalysisCaseDefinitionExtensions.cs b/SysML2.NET/Extend/AnalysisCaseDefinitionExtensions.cs index 3737df67..95d6ec42 100644 --- a/SysML2.NET/Extend/AnalysisCaseDefinitionExtensions.cs +++ b/SysML2.NET/Extend/AnalysisCaseDefinitionExtensions.cs @@ -22,6 +22,7 @@ namespace SysML2.NET.Core.POCO.Systems.AnalysisCases { using System; using System.Collections.Generic; + using System.Linq; using SysML2.NET.Core.Core.Types; using SysML2.NET.Core.Root.Namespaces; @@ -82,10 +83,17 @@ internal static class AnalysisCaseDefinitionExtensions /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IExpression ComputeResultExpression(this IAnalysisCaseDefinition analysisCaseDefinitionSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (analysisCaseDefinitionSubject == null) + { + throw new ArgumentNullException(nameof(analysisCaseDefinitionSubject)); + } + + return analysisCaseDefinitionSubject.featureMembership + .OfType() + .FirstOrDefault() + ?.ownedResultExpression; } } diff --git a/SysML2.NET/Extend/AnalysisCaseUsageExtensions.cs b/SysML2.NET/Extend/AnalysisCaseUsageExtensions.cs index a55fe778..3a5947f6 100644 --- a/SysML2.NET/Extend/AnalysisCaseUsageExtensions.cs +++ b/SysML2.NET/Extend/AnalysisCaseUsageExtensions.cs @@ -22,6 +22,7 @@ namespace SysML2.NET.Core.POCO.Systems.AnalysisCases { using System; using System.Collections.Generic; + using System.Linq; using SysML2.NET.Core.Core.Types; using SysML2.NET.Core.Root.Namespaces; @@ -72,10 +73,15 @@ internal static class AnalysisCaseUsageExtensions /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IAnalysisCaseDefinition ComputeAnalysisCaseDefinition(this IAnalysisCaseUsage analysisCaseUsageSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return analysisCaseUsageSubject == null + ? throw new ArgumentNullException(nameof(analysisCaseUsageSubject)) + : analysisCaseUsageSubject.OwnedRelationship + .OfType() + .Select(featureTyping => featureTyping.Type) + .OfType() + .FirstOrDefault(); } /// @@ -99,10 +105,17 @@ internal static IAnalysisCaseDefinition ComputeAnalysisCaseDefinition(this IAnal /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IExpression ComputeResultExpression(this IAnalysisCaseUsage analysisCaseUsageSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (analysisCaseUsageSubject == null) + { + throw new ArgumentNullException(nameof(analysisCaseUsageSubject)); + } + + return analysisCaseUsageSubject.featureMembership + .OfType() + .FirstOrDefault() + ?.ownedResultExpression; } } diff --git a/SysML2.NET/Extend/CaseDefinitionExtensions.cs b/SysML2.NET/Extend/CaseDefinitionExtensions.cs index 93025367..e885e273 100644 --- a/SysML2.NET/Extend/CaseDefinitionExtensions.cs +++ b/SysML2.NET/Extend/CaseDefinitionExtensions.cs @@ -22,6 +22,7 @@ namespace SysML2.NET.Core.POCO.Systems.Cases { using System; using System.Collections.Generic; + using System.Linq; using SysML2.NET.Core.Core.Types; using SysML2.NET.Core.Root.Namespaces; @@ -78,10 +79,11 @@ internal static class CaseDefinitionExtensions /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeActorParameter(this ICaseDefinition caseDefinitionSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return caseDefinitionSubject == null + ? throw new ArgumentNullException(nameof(caseDefinitionSubject)) + : [..caseDefinitionSubject.featureMembership.OfType().Select(actorMembership => actorMembership.ownedActorParameter)]; } /// @@ -106,10 +108,17 @@ internal static List ComputeActorParameter(this ICaseDefinition case /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IRequirementUsage ComputeObjectiveRequirement(this ICaseDefinition caseDefinitionSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (caseDefinitionSubject == null) + { + throw new ArgumentNullException(nameof(caseDefinitionSubject)); + } + + return caseDefinitionSubject.featureMembership + .OfType() + .FirstOrDefault() + ?.ownedObjectiveRequirement; } /// @@ -132,10 +141,18 @@ internal static IRequirementUsage ComputeObjectiveRequirement(this ICaseDefiniti /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IUsage ComputeSubjectParameter(this ICaseDefinition caseDefinitionSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (caseDefinitionSubject == null) + { + throw new ArgumentNullException(nameof(caseDefinitionSubject)); + } + + var subjectMems = caseDefinitionSubject.featureMembership.OfType().ToList(); + + return subjectMems.Count == 0 + ? null + : subjectMems[0].ownedSubjectParameter; } } diff --git a/SysML2.NET/Extend/CaseUsageExtensions.cs b/SysML2.NET/Extend/CaseUsageExtensions.cs index 793d26f8..6b461a78 100644 --- a/SysML2.NET/Extend/CaseUsageExtensions.cs +++ b/SysML2.NET/Extend/CaseUsageExtensions.cs @@ -22,6 +22,7 @@ namespace SysML2.NET.Core.POCO.Systems.Cases { using System; using System.Collections.Generic; + using System.Linq; using SysML2.NET.Core.Core.Types; using SysML2.NET.Core.Root.Namespaces; @@ -80,10 +81,11 @@ internal static class CaseUsageExtensions /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeActorParameter(this ICaseUsage caseUsageSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return caseUsageSubject == null + ? throw new ArgumentNullException(nameof(caseUsageSubject)) + : [..caseUsageSubject.featureMembership.OfType().Select(actorMembership => actorMembership.ownedActorParameter)]; } /// @@ -95,10 +97,15 @@ internal static List ComputeActorParameter(this ICaseUsage caseUsage /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static ICaseDefinition ComputeCaseDefinition(this ICaseUsage caseUsageSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return caseUsageSubject == null + ? throw new ArgumentNullException(nameof(caseUsageSubject)) + : caseUsageSubject.OwnedRelationship + .OfType() + .Select(featureTyping => featureTyping.Type) + .OfType() + .FirstOrDefault(); } /// @@ -123,10 +130,17 @@ internal static ICaseDefinition ComputeCaseDefinition(this ICaseUsage caseUsageS /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IRequirementUsage ComputeObjectiveRequirement(this ICaseUsage caseUsageSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (caseUsageSubject == null) + { + throw new ArgumentNullException(nameof(caseUsageSubject)); + } + + return caseUsageSubject.featureMembership + .OfType() + .FirstOrDefault() + ?.ownedObjectiveRequirement; } /// @@ -149,10 +163,18 @@ internal static IRequirementUsage ComputeObjectiveRequirement(this ICaseUsage ca /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IUsage ComputeSubjectParameter(this ICaseUsage caseUsageSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (caseUsageSubject == null) + { + throw new ArgumentNullException(nameof(caseUsageSubject)); + } + + var subjects = caseUsageSubject.featureMembership.OfType().ToList(); + + return subjects.Count == 0 + ? null + : subjects[0].ownedSubjectParameter; } } diff --git a/SysML2.NET/Extend/VerificationCaseDefinitionExtensions.cs b/SysML2.NET/Extend/VerificationCaseDefinitionExtensions.cs index 74719965..b1d11783 100644 --- a/SysML2.NET/Extend/VerificationCaseDefinitionExtensions.cs +++ b/SysML2.NET/Extend/VerificationCaseDefinitionExtensions.cs @@ -22,6 +22,7 @@ namespace SysML2.NET.Core.POCO.Systems.VerificationCases { using System; using System.Collections.Generic; + using System.Linq; using SysML2.NET.Core.Core.Types; using SysML2.NET.Core.Root.Namespaces; @@ -82,10 +83,20 @@ internal static class VerificationCaseDefinitionExtensions /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeVerifiedRequirement(this IVerificationCaseDefinition verificationCaseDefinitionSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (verificationCaseDefinitionSubject == null) + { + throw new ArgumentNullException(nameof(verificationCaseDefinitionSubject)); + } + + var objective = verificationCaseDefinitionSubject.objectiveRequirement; + + return objective == null + ? [] + : [..objective.featureMembership + .OfType() + .Select(requirementVerificationMembership => requirementVerificationMembership.verifiedRequirement)]; } } diff --git a/SysML2.NET/Extend/VerificationCaseUsageExtensions.cs b/SysML2.NET/Extend/VerificationCaseUsageExtensions.cs index b71a4229..23a9fcc2 100644 --- a/SysML2.NET/Extend/VerificationCaseUsageExtensions.cs +++ b/SysML2.NET/Extend/VerificationCaseUsageExtensions.cs @@ -22,6 +22,7 @@ namespace SysML2.NET.Core.POCO.Systems.VerificationCases { using System; using System.Collections.Generic; + using System.Linq; using SysML2.NET.Core.Core.Types; using SysML2.NET.Core.Root.Namespaces; @@ -72,10 +73,15 @@ internal static class VerificationCaseUsageExtensions /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static IVerificationCaseDefinition ComputeVerificationCaseDefinition(this IVerificationCaseUsage verificationCaseUsageSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + return verificationCaseUsageSubject == null + ? throw new ArgumentNullException(nameof(verificationCaseUsageSubject)) + : verificationCaseUsageSubject.OwnedRelationship + .OfType() + .Select(featureTyping => featureTyping.Type) + .OfType() + .FirstOrDefault(); } /// @@ -99,10 +105,20 @@ internal static IVerificationCaseDefinition ComputeVerificationCaseDefinition(th /// /// the computed result /// - [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] internal static List ComputeVerifiedRequirement(this IVerificationCaseUsage verificationCaseUsageSubject) { - throw new NotSupportedException("Create a GitHub issue when this method is required"); + if (verificationCaseUsageSubject == null) + { + throw new ArgumentNullException(nameof(verificationCaseUsageSubject)); + } + + var objective = verificationCaseUsageSubject.objectiveRequirement; + + return objective == null + ? [] + : [..objective.featureMembership + .OfType() + .Select(requirementVerificationMembership => requirementVerificationMembership.verifiedRequirement)]; } }