Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 79 additions & 2 deletions .claude/commands/implement-extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,19 @@ From `$ARGUMENTS = C:\CODE\SysML2.NET\SysML2.NET\Extend\<FOO>Extensions.cs`:
- **Subject param name**: lowercase first char of `<FOO>` + `<FOO>[1..]` + `Subject` (e.g. `Type` → `typeSubject`, `Feature` → `featureSubject`).
- **Notes file**: `C:\CODE\SysML2.NET\.team-notes\<foo>-extensions-spec.md` (kebab-case `<foo>`). Create the `.team-notes\` directory if it doesn't exist (`mkdir -p`).
- **Team name**: `<foo>-extensions-impl`.

Print the derived paths back to the user as a sanity check.
- **GitHub issue number**: discover via
```bash
gh issue list --repo STARIONGROUP/SysML2.NET --state all \
--search "SysML2.NET/Extend/<FOO>Extensions.cs in:body" \
--json number,title,state --limit 5
```
Pick the single match. If 0 results or >1 results, surface to the user and
ask for an explicit issue number before proceeding (do not guess).
Search-by-body is preferred over search-by-title because the body always
contains the canonical source-path string and is therefore unambiguous.
- **Issue URL**: `https://github.com/STARIONGROUP/SysML2.NET/issues/<num>`.

Print the derived paths (including the issue URL) back to the user as a sanity check.

### 3. Enumerate stub methods

Expand Down Expand Up @@ -298,9 +309,70 @@ Report to the user:
consider a follow-up issue").
- Spec-text-only methods (e.g. `IsConjugated`) — flag separately so the user
knows the implementation is grounded in spec prose rather than OCL.
- **Issue checklist sync**: `<issue-url>` — `<newly-ticked>` newly ticked,
`<newly-added>` newly added, `<ticked>/<total>` total (filled in after step 11).

Do NOT auto-commit. The user reviews and commits.

### 11. Sync GitHub issue checklist

Runs **unconditionally** after step 10, even on reviewer NEEDS FIX. Rationale:
the issue should always reflect the current implementation state of the file;
unresolved findings are separately surfaced in the final-summary report. The
`gh issue edit` push must touch ONLY the `### Checklist` section.

1. **Fetch current issue body**:
```bash
gh issue view <issue-number> --repo STARIONGROUP/SysML2.NET --json body -q .body > <tmp-old-body>
```
Read the file with the Read tool. Locate the `### Checklist` section
(everything between that header and the next `### ` header or EOF).

2. **Enumerate implementation status from the production file**. For each
`Compute*` method declared in `{{PRODUCTION_FILE}}`:
- Extract the full extension-method signature in the same format the
existing checklist uses (return type + method name + `(this <Iface>[, …])`),
e.g. `List<IClassifier> ComputeDefinition(this IUsage)`.
- Mark **implemented** when BOTH of the following hold:
- The method body does not contain `throw new NotSupportedException`.
- The targeted-fixture test for it (`Verify{MethodName}` in
`{{TEST_FILE}}`) passed in step 7's last `dotnet test` run. Use the
output already captured; do NOT re-run tests.

3. **Compute the new Checklist section**:
- For each existing checklist item: tick it (`- [x]`) if the corresponding
method is implemented per (2); otherwise leave its current state.
- For each `Compute*` method in `{{PRODUCTION_FILE}}` whose signature is
not present in the checklist (use exact-string match on the back-tick
content), append it as a new line. Tick it iff implemented.
- Preserve the relative order of pre-existing items. Append new items
after the last existing item, in declaration order from the production
file.
- Do NOT remove any existing item, even if its signature no longer matches
a method in the production file (signature drift is the user's call).

4. **Stitch the new body**: replace ONLY the lines under `### Checklist`
(up to the next `### ` header or EOF) with the recomputed block.
Everything else — `### Prerequisites`, `### Description`,
`### System Configuration`, blank lines, trailing whitespace — is
preserved verbatim. Use a small string-slice (not a regex rewrite of the
whole body) to minimize the diff.

5. **Push the update**:
```bash
gh issue edit <issue-number> --repo STARIONGROUP/SysML2.NET --body-file <tmp-new-body>
```
Use `--body-file` (not `--body "..."`) so multi-line content survives the
shell unchanged.

6. **Verify**: re-fetch with `gh issue view` and diff against the version you
pushed. If the diff is non-empty outside the Checklist section, abort and
surface to the user — do NOT push a second time.

7. **Report** back into the step-10 final-summary line: issue URL, count of
newly ticked items, count of newly added items, and the resulting
`<ticked>/<total>` ratio.

## Notes for the orchestrator (you, the main agent)

- Pick the model per role using the complexity-grading rubric in step 3.5.
Expand All @@ -326,3 +398,8 @@ Do NOT auto-commit. The user reviews and commits.
- If a build error involves an explicit-interface-impl loop (e.g. `(INamespace)x`
cast not bypassing virtual dispatch), call the static extension method directly
rather than via interface dispatch — pattern from the TypeExtensions task fix.
- The step-11 GitHub-issue sync runs **unconditionally** after step 10, even on
reviewer NEEDS FIX. Rationale: the issue should always reflect the current
implementation state of the file; unresolved findings are separately surfaced
in the final-summary report. The `gh issue edit` push must touch ONLY the
`### Checklist` section — verify with a re-fetch + diff before reporting "done".
2 changes: 1 addition & 1 deletion .github/workflows/nuget-reference-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Setup .NET Environment
uses: actions/setup-dotnet@v5.1.0
with:
dotnet-version: '10.0.107'
dotnet-version: '10.0.x'

- name: Check for outdated packages
id: outdated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ public void Verify_that_textual_notation_is_produced_from_Quantities_root_namesp
Assert.That(textualNotation, Does.Contain("private import ScalarValues::NumericalValue"));
Assert.That(textualNotation, Does.Contain("isBound: Boolean"));
Assert.That(textualNotation, Does.Contain(":>> elements"));
Assert.That(textualNotation, Does.Contain("assert constraint orderSum"));
Assert.That(textualNotation, Does.Contain("assert constraint boundMatch"));
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,15 +208,37 @@ private static IElement QueryCurrentOwnedRelationship(IElement element, TextualN
/// all inherit transitively (PerformActionUsage/ExhibitStateUsage/IncludeUseCaseUsage are
/// <see cref="IActionUsage"/> descendants, AssertConstraintUsage inherits <see cref="IConstraintUsage"/>,
/// SatisfyRequirementUsage inherits <see cref="IRequirementUsage"/>).</para>
/// <para>Several other <see cref="IActionUsage"/> descendants are NOT BehaviorUsageElement
/// alternatives — they belong to the sibling <c>ActionNodeMember</c> rule
/// (<see cref="IControlNode"/>, <see cref="ISendActionUsage"/>, <see cref="IAcceptActionUsage"/>,
/// <see cref="IAssignmentActionUsage"/>, <see cref="ITerminateActionUsage"/>,
/// <see cref="IIfActionUsage"/>, <see cref="ILoopActionUsage"/>) or to the
/// <c>TransitionUsage</c> / <c>TargetTransitionUsage</c> rules
/// (<see cref="ITransitionUsage"/>). Without the explicit exclusions below, a membership
/// owning e.g. a <see cref="IControlNode"/> would be silently mis-routed to
/// <c>BuildBehaviorUsageMember</c> by the dispatchers at
/// <c>FeatureMembershipTextualNotationBuilder.cs:271</c> and
/// <c>TypeTextualNotationBuilder.cs:289/318</c>, emitting the literal <c>action</c> keyword
/// instead of the expected ActionNode keyword (<c>merge</c>, <c>fork</c>, <c>decision</c>,
/// <c>join</c>, <c>send</c>, <c>accept</c>, <c>assign</c>, <c>terminate</c>, <c>if</c>,
/// <c>while</c>, <c>for</c>) or the transition keyword.</para>
/// </summary>
/// <param name="featureMembership">The <see cref="IFeatureMembership"/></param>
/// <param name="writerContext">The active <see cref="TextualNotationWriterContext"/> (unused for this guard)</param>
/// <returns>True if the membership owns a behavior-usage element</returns>
/// <returns>True if the membership owns a behavior-usage element (and that element is not routed by a more specific sibling rule)</returns>
internal static bool IsValidForBehaviorUsageMember(this IFeatureMembership featureMembership, TextualNotationWriterContext writerContext)
{
return featureMembership?.OwnedRelatedElement.Any(element =>
element is IActionUsage or IStateUsage or IConstraintUsage
or IRequirementUsage or ICaseUsage) == true;
(element is IActionUsage or IStateUsage or IConstraintUsage
or IRequirementUsage or ICaseUsage)
&& element is not IControlNode
&& element is not ISendActionUsage
&& element is not IAcceptActionUsage
&& element is not IAssignmentActionUsage
&& element is not ITerminateActionUsage
&& element is not IIfActionUsage
&& element is not ILoopActionUsage
&& element is not ITransitionUsage) == true;
}

/// <summary>
Expand Down Expand Up @@ -270,29 +292,54 @@ internal static bool IsValidForBinaryInterfacePart(this IInterfaceUsage interfac
/// <para>Matches any structural-usage metaclass. IndividualUsage/PortionUsage/EventOccurrenceUsage
/// inherit <see cref="IOccurrenceUsage"/>; Message and SuccessionFlowUsage inherit
/// <see cref="IFlowUsage"/>.</para>
/// <para>The BehaviorUsageElement subtypes (<see cref="IActionUsage"/>, <see cref="IConstraintUsage"/>
/// and their descendants) ALSO inherit <see cref="IOccurrenceUsage"/> via the metaclass hierarchy
/// (e.g. <c>IConstraintUsage : IOccurrenceUsage</c>, <c>IActionUsage : IOccurrenceUsage</c>),
/// so the bare <c>is IOccurrenceUsage</c> would silently pull them in. They must be explicitly
/// excluded so the upstream dispatcher (<c>BuildOccurrenceUsageElement</c>) routes them to
/// <c>BuildBehaviorUsageElement</c> instead — without this exclusion they fall through the
/// <c>BuildStructureUsageElement</c> switch to the bare <c>case IOccurrenceUsage → BuildPortionUsage</c>
/// arm, which mis-renders constraints as <c>#name;</c>.</para>
/// <para><see cref="IFlowUsage"/> is itself <see cref="IActionUsage"/> (per
/// <c>IFlowUsage : IConnectorAsUsage, IFlow, IActionUsage</c>), but it is a
/// StructureUsageElement alternative, so the IActionUsage exclusion makes an explicit
/// IFlowUsage exception.</para>
/// </summary>
/// <param name="usage">The <see cref="IUsage"/></param>
/// <param name="writerContext">The active <see cref="TextualNotationWriterContext"/> (unused for this guard)</param>
/// <returns>True if the usage is a structural-usage metaclass</returns>
/// <returns>True if the usage is a structural-usage metaclass (and not a behavior-usage subtype routed by a sibling rule)</returns>
internal static bool IsValidForStructureUsageElement(this IUsage usage, TextualNotationWriterContext writerContext)
{
return usage is IOccurrenceUsage or IItemUsage or IPartUsage or IViewUsage
or IRenderingUsage or IPortUsage or IConnectionUsage or IInterfaceUsage
or IAllocationUsage or IFlowUsage;
return (usage is IOccurrenceUsage or IItemUsage or IPartUsage or IViewUsage
or IRenderingUsage or IPortUsage or IConnectionUsage or IInterfaceUsage
or IAllocationUsage or IFlowUsage)
&& usage is not IConstraintUsage
&& (usage is not IActionUsage || usage is IFlowUsage);
}

/// <summary>
/// Asserts that the <see cref="IOccurrenceUsage"/> is valid for the OccurrenceUsage rule.
/// <para><c>OccurrenceUsage = OccurrenceUsagePrefix 'occurrence' Usage</c> — the general case
/// where neither <c>isIndividual</c> nor <c>portionKind</c> has been set by one of the more
/// specific rules (<c>IndividualUsage</c>, <c>PortionUsage</c>).</para>
/// <para>The bare-keyword form must NOT match runtime types covered by sibling rules in
/// <c>StructureUsageElement</c> (<see cref="IItemUsage"/>, <see cref="IPortUsage"/>,
/// <see cref="IEventOccurrenceUsage"/>) or <c>BehaviorUsageElement</c>
/// (<see cref="IActionUsage"/>, <see cref="IConstraintUsage"/>) — each of those has its
/// own dedicated grammar rule and dispatch arm. Without these exclusions, a constraint
/// or action would be silently rendered as <c>occurrence&#160;name;</c>.</para>
/// </summary>
/// <param name="occurrenceUsage">The <see cref="IOccurrenceUsage"/></param>
/// <param name="writerContext">The active <see cref="TextualNotationWriterContext"/> (unused for this guard)</param>
/// <returns>True if the usage is a plain occurrence (no individual, no portion kind)</returns>
/// <returns>True if the usage is a plain occurrence (no individual, no portion kind, and not routed by a more specific sibling rule)</returns>
internal static bool IsValidForOccurrenceUsage(this IOccurrenceUsage occurrenceUsage, TextualNotationWriterContext writerContext)
{
return occurrenceUsage is { IsIndividual: false, PortionKind: null };
return occurrenceUsage is { IsIndividual: false, PortionKind: null }
&& occurrenceUsage is not IItemUsage
&& occurrenceUsage is not IPortUsage
&& occurrenceUsage is not IActionUsage
&& occurrenceUsage is not IConstraintUsage
&& occurrenceUsage is not IEventOccurrenceUsage;
}

/// <summary>
Expand Down
Loading
Loading