diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 337f9ec..7ccda75 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -61,6 +61,12 @@ "commands": [ "fileassert" ] + }, + "demaconsulting.sysml2tools.tool": { + "version": "0.1.0-beta.5", + "commands": [ + "sysml2tools" + ] } } } \ No newline at end of file diff --git a/.cspell.yaml b/.cspell.yaml index a328c5d..e2e75fe 100644 --- a/.cspell.yaml +++ b/.cspell.yaml @@ -35,6 +35,9 @@ words: - sonarmark - sonarqube - sonarscanner + - sysml + - sysml2tools + - unwritable - versionmark - Weasy - Weasyprint diff --git a/.github/agents/developer.agent.md b/.github/agents/developer.agent.md index 5f208eb..5f2b988 100644 --- a/.github/agents/developer.agent.md +++ b/.github/agents/developer.agent.md @@ -19,7 +19,7 @@ Perform software development tasks by determining and applying appropriate stand If one exists, fetch it as the starting point; adjust placeholder names and heading depth to match the target path before writing the file - For each modified file, identify which companion artifacts need updating - (requirements, design docs, tests, review-sets) + (requirements, design docs, verification docs, tests, review-sets, README.md, user guides) - Include companion artifact updates in the work plan 4. **Execute work** following standards requirements and quality checks 5. **Formatting**: Run `pwsh ./fix.ps1` to silently apply all @@ -35,8 +35,7 @@ Perform software development tasks by determining and applying appropriate stand # Developer Agent Report **Result**: (SUCCEEDED|FAILED) - -## Work Summary +**Report**: `.agent-logs/developer-{subject}-{unique-id}.md` - **Files Modified**: {List of files created/modified/deleted} - **Languages Detected**: {Languages identified} diff --git a/.github/agents/formal-review.agent.md b/.github/agents/formal-review.agent.md index 7dd8e84..cb78ddf 100644 --- a/.github/agents/formal-review.agent.md +++ b/.github/agents/formal-review.agent.md @@ -31,12 +31,14 @@ standards from the selection matrix in AGENTS.md. 1. Download the review checklist from . If the download fails, report the failure rather than proceeding without the template. -2. Use `dotnet reviewmark --elaborate {review-set}` to get the files to review -3. Review all files holistically, checking for cross-file consistency and - compliance with the review checklist -4. Save the populated review checklist to `.agent-logs/reviews/review-report-{review-set}.md`. +2. Run `dotnet reviewmark --elaborate {review-set}`. Read all files listed under + `## Context` first — these are reference material, not under review — then review + all files listed under `## Files` holistically, using the context to understand + the intended role and scope within the broader system, and checking for cross-file + consistency and compliance with the review checklist. +3. Save the populated review checklist to `.agent-logs/reviews/review-report-{review-set}.md`. This directory holds formal review artifacts, not agent logs. -5. Generate a completion report per the AGENTS.md reporting requirements. +4. Generate a completion report per the AGENTS.md reporting requirements. # Report Template @@ -44,6 +46,7 @@ standards from the selection matrix in AGENTS.md. # Formal Review Report **Result**: (SUCCEEDED|FAILED) +**Report**: `.agent-logs/formal-review-{subject}-{unique-id}.md` ## Review Summary diff --git a/.github/agents/implementation.agent.md b/.github/agents/implementation.agent.md index 7cc0352..d94e880 100644 --- a/.github/agents/implementation.agent.md +++ b/.github/agents/implementation.agent.md @@ -28,36 +28,18 @@ The state-transitions include retrying a limited number of times: ## PLANNING State (start) -Call the **explore** agent as a sub-agent (built-in agent type) with: +Call the **planning** agent as a sub-agent (custom agent from `.github/agents/`) with: - **context**: the user's request + any previous quality findings + retry context -- **goal**: produce a verified implementation plan through these steps: - - 1. Investigate the codebase and develop a concrete implementation plan that - addresses the request - 2. **Identify companion artifact deliverables**: for every code change in the - plan, list the requirements files, design documents, and review-set entries - that must be created or updated - traceability must flow requirements → - design → code, so these are mandatory deliverables, not optional extras - 3. Review the plan for assumptions, weaknesses, and gaps - identify up to 5 - key assumptions and rate each as: - - **VERIFIED**: confirmed by codebase evidence - - **LIKELY**: consistent with codebase patterns but not directly confirmed - - **UNVERIFIED**: not confirmed by any evidence - 4. For any assumption rated UNVERIFIED or LIKELY, attempt to resolve it - through additional investigation and revise the plan to address identified - weaknesses - repeat the critique-and-strengthen cycle up to 2 additional - times if unresolved issues remain, but stop as soon as the plan is stable - 5. List up to 5 risks to the implementation - 6. Assess feasibility: can this be implemented in a single development pass? - 7. State a **recommendation**: GO or INCOMPLETE - GO if the plan is sound, or - INCOMPLETE if critical unknowns remain that only the user can resolve - -Once the explore sub-agent finishes: - -- IF recommendation is INCOMPLETE: Transition to REPORT with Result: INCOMPLETE, +- **goal**: produce a verified implementation plan, or a targeted plan to address + the identified quality issues if this is a retry + +Once the planning sub-agent finishes: + +- IF Result is FAILED: Transition to REPORT with Result: FAILED +- IF Result is INCOMPLETE: Transition to REPORT with Result: INCOMPLETE, listing the unknowns and what CAN be implemented once they are resolved -- OTHERWISE (GO): Transition to DEVELOPMENT +- OTHERWISE (SUCCEEDED): Transition to DEVELOPMENT ## DEVELOPMENT State @@ -76,7 +58,8 @@ Once the developer sub-agent finishes: Call the **quality** agent as a sub-agent (custom agent from `.github/agents/`) with: -- **context**: the user's request + development summary + files changed + previous issues (if any) +- **context**: the user's request + development summary + files changed + planning companion artifact table + + previous issues (if any) - **goal**: check the quality of the work performed for any issues Once the quality sub-agent finishes: @@ -92,6 +75,9 @@ Once the quality sub-agent finishes: this agent may report INCOMPLETE when the request cannot be implemented without information only the user can provide. +For full planning details (assumptions, risks, feasibility), read the planning +report file referenced in the planning agent's response. + Generate the completion report using the template below, then save it to `.agent-logs/{agent-name}-{subject}-{unique-id}.md` per the AGENTS.md reporting requirements, and return the summary to the caller. @@ -102,19 +88,20 @@ requirements, and return the summary to the caller. # Implementation Orchestration Report **Result**: (SUCCEEDED|FAILED|INCOMPLETE) -**Final State**: (PLANNING|DEVELOPMENT|QUALITY|REPORT) +**Report**: `.agent-logs/implementation-{subject}-{unique-id}.md` +**Last Active State**: (PLANNING|DEVELOPMENT|QUALITY) **Retry Count**: ## State Machine Execution -- **Planning Results**: {Implementation plan, assumption ratings, risks, and recommendation} +- **Planning Results**: {Planning report path; plan summary and SUCCEEDED/INCOMPLETE/FAILED result} - **Development Results**: {Summary of developer agent results} - **Quality Results**: {Summary of quality agent results} - **State Transitions**: {Log of state changes and decisions} ## Sub-Agent Coordination -- **Explore Agent (Planning)**: {Plan, assumption verdicts, top risks, GO/INCOMPLETE recommendation} +- **Planning Agent**: {Report file path, SUCCEEDED/INCOMPLETE/FAILED result, plan summary} - **Developer Agent**: {Development status and files modified} - **Quality Agent**: {Validation results and compliance status} @@ -123,4 +110,9 @@ requirements, and return the summary to the caller. - **Implementation Success**: {Overall completion status} - **Quality Compliance**: {Final quality validation status} - **Issues Resolved**: {Problems encountered and resolution attempts} + +## Unknowns (only when Result is INCOMPLETE) + +- **Unresolved Questions**: {List each question the user must answer} +- **What Can Proceed**: {Work that can be done without the missing information} ``` diff --git a/.github/agents/lint-fix.agent.md b/.github/agents/lint-fix.agent.md index 549e751..36d3ca1 100644 --- a/.github/agents/lint-fix.agent.md +++ b/.github/agents/lint-fix.agent.md @@ -68,8 +68,7 @@ submission, not during normal development. # Lint Fix Report **Result**: (SUCCEEDED|FAILED) - -## Summary +**Report**: `.agent-logs/lint-fix-{subject}-{unique-id}.md` - **Iterations**: {Number of fix-loop iterations performed} - **Files Modified**: {List of all files changed} diff --git a/.github/agents/planning.agent.md b/.github/agents/planning.agent.md new file mode 100644 index 0000000..20e75ee --- /dev/null +++ b/.github/agents/planning.agent.md @@ -0,0 +1,134 @@ +--- +name: planning +description: Planning agent that investigates the codebase, develops a verified implementation plan, and identifies all companion artifact deliverables. +user-invocable: true +--- + +# Planning Agent + +Investigate the codebase and produce a verified implementation plan with all +companion artifact deliverables. + +## Step 1 — Load Standards + +Read the relevant standards from `.github/standards/` using the selection matrix +in `AGENTS.md` based on the artifact types in scope for the request (requirements, +design, verification, documentation, code). + +## Step 2 — Investigate and Plan + +Read `docs/design/introduction.md` first (if present), then investigate the +codebase to develop a concrete implementation plan: + +- Identify all files to create, modify, or delete +- Describe the change required for each file + +## Step 3 — Identify Companion Artifact Deliverables + +For each planned change, assess the mandatory companion artifacts below (create/update/N/A +with justification): + +- **Requirements** — functional changes require a requirement entry +- **Design Documentation** — new or changed components require design docs +- **Verification Documentation** — new or changed components require verification docs +- **Tests** — functional changes require test coverage +- **Review Sets** — changes to the software item hierarchy (units or subsystems + added, removed, or reorganized) require review-set updates +- **README.md** — user-facing changes require README updates +- **User Guide** — user-facing features require user guide updates + +## Step 4 — Critique and Strengthen + +Identify up to 5 key assumptions and rate each: + +- **VERIFIED**: confirmed by codebase evidence +- **LIKELY**: consistent with codebase patterns but not directly confirmed +- **UNVERIFIED**: not confirmed by any evidence + +For UNVERIFIED or LIKELY assumptions, investigate further and revise the plan. +Repeat up to 2 more times, stopping when the plan is stable. + +## Step 5 — Risk Assessment + +List up to 5 risks with a brief mitigation for each. + +## Step 6 — Feasibility Assessment + +State whether this can be implemented in a single development pass and any +preconditions that affect feasibility. + +## Step 7 — Recommendation + +- **SUCCEEDED** — the plan is sound and the developer agent can proceed +- **INCOMPLETE** — critical unknowns remain that only the user can resolve; + list each unknown explicitly +- **FAILED** — investigation could not produce a viable plan + +# REPORT Phase + +Save the full analysis to `.agent-logs/planning-{subject}-{unique-id}.md` per +the AGENTS.md reporting requirements. + +Then respond to the caller with ONLY the lean structured summary below. + +# Report Template + +```markdown +# Planning Report + +**Result**: (SUCCEEDED|INCOMPLETE|FAILED) +**Request Summary**: {Brief restatement of the task as understood} +**Report**: `.agent-logs/planning-{subject}-{unique-id}.md` + +## Implementation Plan + +| File | Action | Description | +|------|--------|-------------| +| {path} | create/modify/delete | {what changes and why} | + +## Companion Artifact Deliverables + +| Category | File | Action | +|----------|------|--------| +| Requirements | {path} | create/update/N/A — {justification} | +| Design Documentation | {path} | create/update/N/A — {justification} | +| Verification Documentation | {path} | create/update/N/A — {justification} | +| Tests | {path} | create/update/N/A — {justification} | +| Review Sets | {path} | create/update/N/A — {justification} | +| README.md | {path} | create/update/N/A — {justification} | +| User Guide | {path} | create/update/N/A — {justification} | + +## Assumption Analysis + +| # | Assumption | Rating | Resolution | +|---|-----------|--------|------------| +| 1 | {assumption} | VERIFIED/LIKELY/UNVERIFIED | {resolution or N/A} | + +## Risk Assessment + +1. **[severity]** {risk} — {mitigation} + +## Feasibility Assessment + +{Single-pass or not, and why. Any preconditions.} + +## Unknowns + +{Only present when Result is INCOMPLETE. List each question the user must +resolve before implementation can proceed.} +``` + +# Lean Structured Response (returned to caller) + +```markdown +**Result**: (SUCCEEDED|INCOMPLETE|FAILED) +**Report**: `.agent-logs/planning-{subject}-{unique-id}.md` + +**Plan**: +{Repeat the Implementation Plan table} + +**Companion Artifacts**: +{Repeat the Companion Artifact Deliverables table} + +**Unknowns**: {Only if INCOMPLETE — list questions for the user} +``` diff --git a/.github/agents/quality.agent.md b/.github/agents/quality.agent.md index 380d11f..26fd251 100644 --- a/.github/agents/quality.agent.md +++ b/.github/agents/quality.agent.md @@ -13,14 +13,23 @@ Grade and validate software development work by ensuring compliance with project 1. **Analyze the task request AND completed work** to determine scope: identify which artifact categories were changed, and which *should have been changed* given the task - new user-visible features always require requirements, - design, and review-set coverage regardless of whether those files were touched; - test-only additions (corner-case tests, defensive boundary tests, regression - tests) do not require a corresponding requirement + design, verification docs, and README/user guide updates regardless of + whether those files were touched; Review Sets are always in scope when + the software item hierarchy changes (units or subsystems added, removed, or + reorganized); test-only additions (corner-case tests, defensive boundary + tests, regression tests) do not require a corresponding requirement; if a + planning companion artifact table is provided in context, cross-reference it + — any artifact listed as create/update must be covered in the evaluation and + FAIL if the artifact was not produced 2. **Read relevant standards** using the selection matrix in AGENTS.md 3. **Evaluate all in-scope categories** - N/A only when the task genuinely cannot affect a category; if the task introduces new user-visible features or - structural changes then Requirements, Design Documentation, and Review - Management are always in scope and FAIL if the artifacts were not updated + structural changes then Requirements, Design Documentation, and Verification + Documentation are always in scope and FAIL if the artifacts were not updated; + Documentation (README/user guide) is always in scope for user-facing changes + and FAIL if not updated; Review Sets are always in scope when the + software item hierarchy changes (units or subsystems added, removed, or + reorganized) and FAIL if review-sets were not updated 4. **Validate tool compliance** using ReqStream, ReviewMark, and build tools 5. **Generate focused quality report** per the AGENTS.md reporting requirements - save to `.agent-logs/{agent-name}-{subject}-{unique-id}.md` and return the summary to the caller @@ -36,6 +45,7 @@ For each checklist item in the template below, record as `(PASS|FAIL|N/A) - {one # Quality Assessment Report **Result**: (SUCCEEDED|FAILED) +**Report**: `.agent-logs/quality-{subject}-{unique-id}.md` **Overall Grade**: (PASS|FAIL) ## Required Fixes (only when Result is FAILED) @@ -50,13 +60,13 @@ Priority-ordered list of issues that MUST be resolved for the next retry: - **Evaluated**: {List sections assessed and why} - **Skipped**: {One-line per skipped section with reason, e.g., "Design - Documentation: N/A - no design files modified"} + Documentation: N/A - no component behavior, structure, or interface changed"} ## Requirements Compliance: (PASS|FAIL|N/A) - Were requirements created/updated for all functional changes? - Were source filters applied for platform-specific requirements? -- Is requirements traceability maintained to tests? +- Is forward traceability from requirements to verification artifacts preserved? ## Design Documentation Compliance: (PASS|FAIL|N/A) @@ -80,7 +90,14 @@ Priority-ordered list of issues that MUST be resolved for the next retry: - Are cross-hierarchy test dependencies documented in design docs? - Do all tests pass? -## Review Management Compliance: (PASS|FAIL|N/A) +## Verification Documentation Compliance: (PASS|FAIL|N/A) + +- Were verification documents created/updated for all new or changed components? +- Do verification documents include all mandatory sections (Verification Approach, Test Environment, + Acceptance Criteria, Test Scenarios)? +- Is requirements-to-test coverage tracked via the ReqStream trace matrix (not embedded in verification docs)? + +## Review Sets Compliance: (PASS|FAIL|N/A) - Were review-sets updated for structural changes? - Is review scope appropriate for change magnitude? diff --git a/.github/agents/template-sync.agent.md b/.github/agents/template-sync.agent.md index 44d29ff..baf5fa8 100644 --- a/.github/agents/template-sync.agent.md +++ b/.github/agents/template-sync.agent.md @@ -21,19 +21,37 @@ Delegate each group to a sub-agent. # Work Groups - **Root config files** - all non-collection files at the repository root +- **`docs/sysml2/`** - SysML2 model files and rendered views (root-level flat folder, + not a Pandoc collection) - **One group per flat `docs/` folder** - e.g. `docs/build_notes/`, `docs/user_guide/` +- **One group for root files in each of `docs/design/`, `docs/verification/`, + `docs/reqstream/`** - e.g. `docs/design/introduction.md` — separate from the + system subtrees beneath them - **One group per system subtree** in `docs/design/`, `docs/verification/`, `docs/reqstream/` - each subtree and all its descendants is one group # Orchestration -For each group intersecting the requested scope, call a sub-agent with: +For Audit mode, call an **explore** sub-agent (built-in) per group. +For Sync, Scaffold, and Recreate modes, call a **general-purpose** sub-agent (built-in) per group. + +For each group intersecting the requested scope, call the appropriate sub-agent with: - **context**: - Group scope and template URL from the `# Reference Template` section in `AGENTS.md` + - Applicable standards from the `# Standards Application` matrix in `AGENTS.md` + for the file types in the group scope - Project-specific names substitute for placeholders at matching path depth - (e.g. `SystemName` → `{SystemName}`, `system-name` → `{system-name}`) - - If a template counterpart cannot be fetched, skip the file and report it + (e.g. `MySystem` → `{SystemName}`, `my-system` → `{system-name}`) + - For files within `{system-name}/` subtrees in `docs/design/`, `docs/verification/`, + and `docs/reqstream/`: consult `docs/design/introduction.md` to determine whether + each item is a subsystem or unit, then select the appropriate template + (`subsystem-name.*` or `unit-name.*`) regardless of the item's folder depth — + do not infer item type from path depth alone + - If a file has no template counterpart, skip it and report it as + "No template found" — this is not a failure + - If a file appears in `repository-map.md` but its template cannot be fetched, + report Result: FAILED and list the affected files - **goal**: - Based on the given mode: - **Audit** - fetch each template counterpart; compare headings; report missing @@ -42,15 +60,54 @@ For each group intersecting the requested scope, call a sub-agent with: - **Scaffold** - fetch `repository-map.md` from the template URL in `AGENTS.md` to identify files that should exist but don't; for each, fetch the template, populate all sections, write the file; run `pwsh ./fix.ps1` - - **Recreate** - read the existing file in full, then fetch the template; use - full semantic understanding to map old content onto template sections, - splitting or consolidating as needed; create extra sections for any content - that has no template home; write the rebuilt file; run `pwsh ./fix.ps1` - - When writing any section: HTML comments and TODO placeholders in the template - are instructions - always resolve them to real content; infer from README, - related files, sibling docs, and path; if confident write directly; if - ambiguous offer 2–3 concrete options and ask the user; keep asking until they - answer - never leave a TODO unless the user explicitly requests it + - **Recreate** - fetch the template and use it as the blueprint for a + freshly authored document: + - Work through the template section by section; for each section, find + any `TEMPLATE-DIRECTIVE` blocks (both `` + in markdown and `# ` in YAML) — execute + each directive (read specified standards, apply structural guidance, + substitute content), then **remove the directive block entirely** from + the output; gather the relevant technical details from all available + sources — the old file, README, related docs, sibling files, and any + other repo context — to populate that section correctly; the old file's + structure and headings are irrelevant; only its factual content is mined + as a source + - **Gap-check**: after all template sections are filled, scan the old + file once more for any technical information not yet captured; if + found, preserve it by appending new relevant sections at the end + - **Before writing**: do a mandatory self-check — for every section that + has a `TEMPLATE-DIRECTIVE` block in the template, explicitly state what + format the directive requires, then verify the drafted content matches + that format exactly (e.g. if the directive says "no sub-headings", + confirm there are no `###` headings inside that section; if it says + "bold-name paragraph blocks", confirm each entry is `**Name**: prose` + with no sub-heading); fix any mismatches before writing the file + - Write the rebuilt file; run `pwsh ./fix.ps1` + - When writing any section: `TEMPLATE-DIRECTIVE` blocks are directives — + execute them (read specified standards, apply structural guidance, substitute + content) and **remove the block entirely** from the written file; inline + `TODO:` placeholders in YAML string values (e.g. `title:`, `justification:`) + are content placeholders — always resolve them to real content; infer from + README, related files, sibling docs, and path; if confident write directly; + if ambiguous, **do not ask interactively** — return the unresolved questions + in the result so the orchestrator can ask the user and re-invoke; never leave + a TODO or TEMPLATE-DIRECTIVE in the output unless the user explicitly requests it + - Return results in this format for each file in the group: + + ```markdown + ### {file-path} + + - **Template**: {template path or "not found"} + - **Missing sections**: {list or "none"} + - **Heading depth issues**: {list or "none"} + - **Content format issues**: {list or "none"} *(Recreate only)* + - **Action**: (Reported | Sections added | Created | Rebuilt | No template found) + - **Unresolved Questions**: {list or "none"} + ``` + +If any sub-agent returns unresolved questions, collect them, ask the user, then +re-invoke the affected sub-agent(s) with the answers before assembling the final report. +If questions remain unresolved after asking the user, report Result: INCOMPLETE. Collect sub-agent results and assemble the final report. @@ -59,7 +116,8 @@ Collect sub-agent results and assemble the final report. ```markdown # Template Sync Report -**Result**: (SUCCEEDED|FAILED) +**Result**: (SUCCEEDED|FAILED|INCOMPLETE) +**Report**: `.agent-logs/template-sync-{subject}-{unique-id}.md` **Mode**: (Audit|Sync|Scaffold|Recreate) ## Files @@ -69,9 +127,16 @@ Collect sub-agent results and assemble the final report. - **Template**: {template path} - **Missing sections**: {list or "none"} - **Heading depth issues**: {list or "none"} +- **Content format issues**: {list of sections where intra-section content did not + match the template comment's prescribed format, or "none"} *(Recreate only)* - **Action**: (Reported | Sections added | Created | Rebuilt | No template found) +- **Unresolved Questions**: {list or "none"} ## Summary - **Conformant**: {count} | **Deviations**: {count} | **Updated**: {count} + +## Unknowns (only when Result is INCOMPLETE) + +- **Unresolved Questions**: {List each placeholder or ambiguity the user must resolve} ``` diff --git a/.github/skills/sysml2tools-query/SKILL.md b/.github/skills/sysml2tools-query/SKILL.md new file mode 100644 index 0000000..f754338 --- /dev/null +++ b/.github/skills/sysml2tools-query/SKILL.md @@ -0,0 +1,145 @@ +--- +name: sysml2tools-query +description: Query this repository's SysML2 architecture model (docs/sysml2/) to understand software structure, purpose, and relationships before deep-diving into source code. Use this when asked to understand the codebase, find which unit implements something, assess the impact of a change, or trace requirements to code. +--- + +# SysML2Tools Query Skill + +This repository models its software structure in SysML2 under `docs/sysml2/`. Prefer +querying this model over grepping/reading source files when you need to understand +*what* a piece of the system is, *why* it exists, or *what it depends on*. + +## Prerequisites + +The `sysml2tools` CLI is a local .NET tool pinned in `.config/dotnet-tools.json`. +Restore it once per session if not already available: + +```pwsh +dotnet tool restore +``` + +## Model files + +- `docs/sysml2/model/{system-name}.sysml` — system-level `part def` only (no subsystems/units + inline). A repository may contain more than one system. +- `docs/sysml2/model/{system-name}/{subsystem-name}.sysml` and + `docs/sysml2/model/{system-name}/{subsystem-name}/{unit-name}.sysml` — one file per + Subsystem/Unit, nested to mirror the same folder depth as that item's companion + `docs/design/`, `docs/reqstream/`, and `docs/verification/` files. +- `docs/sysml2/model/ots.sysml` — off-the-shelf (OTS) dependency parts (present when OTS items + exist). +- `docs/sysml2/model/shared.sysml` — Shared Package parts (present when Shared Package items + exist). +- `docs/sysml2/views/design-views.sysml` — named views rendered for the design document; a + sibling of `model/`, not nested inside it. Not usually needed for query workflows, but + useful to see how diagrams are scoped. + +Always pass all `.sysml` files (or a glob covering them) to every `sysml2tools` +invocation — the model spans multiple files and cross-references between them; files that +reopen the same `package Name { ... }` from different files merge into one namespace +automatically, no `import` required. As of `sysml2tools` 0.1.0-beta.5, every subcommand +(`lint`, `render`, `query`) expands `*`/`**` glob patterns internally and recurses +correctly — pass a single **quoted** glob string and let the tool resolve it, rather than +relying on the shell (quoting works identically in both PowerShell and bash): + +```pwsh +dotnet sysml2tools query list 'docs/sysml2/model/**/*.sysml' +``` + +## Artifact-location metadata + +Every System/Subsystem/Unit `part def` carries named `comment` elements pointing at its +companion artifacts (source, test, design, verification, and — for repositories that still +use ReqStream — requirements file). `query describe` returns every comment attached to +an element, so one query gets you every artifact location for that item without grepping the +source tree: + +```pwsh +dotnet sysml2tools query describe -e {SystemName}::{UnitName} 'docs/sysml2/model/**/*.sysml' +``` + +```text +- Documentation: One-line purpose statement for the unit. +- Comment: Source: src/{Project}/{Subsystem}/{UnitName}.cs +- Comment: Test: test/{Project}.Tests/{Subsystem}/{UnitName}Tests.cs +- Comment: Design: docs/design/{system-name}/{subsystem-name}/{unit-name}.md +- Comment: Verification: docs/verification/{system-name}/{subsystem-name}/{unit-name}.md +- Comment: Requirements: docs/reqstream/{system-name}/{subsystem-name}/{unit-name}.yaml +``` + +Not every item has all five comments — Systems/Subsystems have no `Source` comment, and +units without a dedicated companion doc point at their parent's doc instead (see the +Artifact-Location Comments section of `sysml2-modeling.md` for the full rules). Prefer these +paths over guessing a file location from the unit name; fall back to `grep`/`glob` only when +a comment is missing or the model is stale. + +## Recommended workflow + +1. **Discover the system(s) present** — do not assume a fixed name: + + ```pwsh + dotnet sysml2tools query list --kind "part def" 'docs/sysml2/model/**/*.sysml' + ``` + + Look for the top-level part def with no containing part (or the one with the most + children) to identify each system's qualified name. + +2. **Get the full hierarchy** (subsystems and units) for a system found above: + + ```pwsh + dotnet sysml2tools query describe -e 'docs/sysml2/model/**/*.sysml' + ``` + + `describe` lists direct children and every artifact-location comment; repeat on each + child to walk deeper, or use `query list` to see the full flat inventory. + +3. **Understand a specific unit's purpose and find its files** before opening its source + file — `describe` returns both the purpose `doc` and every `Comment:` artifact-location + line in one call (see Artifact-location metadata above): + + ```pwsh + dotnet sysml2tools query describe -e 'docs/sysml2/model/**/*.sysml' + ``` + +4. **Assess impact before editing a unit** — see what depends on it: + + ```pwsh + dotnet sysml2tools query used-by -e 'docs/sysml2/model/**/*.sysml' + dotnet sysml2tools query impact -e 'docs/sysml2/model/**/*.sysml' + ``` + + Note: this only surfaces structural (typing/containment) references modeled in + `.sysml` — not full call-graph/behavioral dependencies. Confirm actual usage in source + before making impact-sensitive changes. + +5. **Trace requirements** linked to a unit, if modeled: + + ```pwsh + dotnet sysml2tools query requirements -e 'docs/sysml2/model/**/*.sysml' + ``` + +6. **Search by name or kind** when the qualified name is unknown: + + ```pwsh + dotnet sysml2tools query find --name 'docs/sysml2/model/**/*.sysml' + dotnet sysml2tools query list --kind "part def" 'docs/sysml2/model/**/*.sysml' + ``` + +Use `--format json` on any query verb for machine-parsed output. + +## Fallback + +If the model is stale, doesn't yet cover a unit, or a query returns nothing useful, fall +back to `grep`/`glob`/reading source directly. When you finish work that adds or changes a +Unit/Subsystem, update the corresponding `.sysml` model file (including its artifact-location +comments) in the same change (mirrors the existing requirement to keep `docs/design/*.md` and +`docs/reqstream/*.yaml` companion artifacts in sync). See the `sysml2-modeling.md` +standard for full modeling and view-authoring conventions. + +## Validating changes to the model + +Before committing changes to any `.sysml` file, lint it: + +```pwsh +dotnet sysml2tools lint 'docs/sysml2/**/*.sysml' +``` diff --git a/.github/standards/coding-principles.md b/.github/standards/coding-principles.md index 9e67fbb..6797c61 100644 --- a/.github/standards/coding-principles.md +++ b/.github/standards/coding-principles.md @@ -3,11 +3,6 @@ name: Coding Principles description: Follow these standards when developing any software code. --- -# Coding Principles Standards - -This document defines universal coding principles and quality standards for software development within -Continuous Compliance environments. - # Core Principles ## Literate Coding @@ -20,10 +15,9 @@ All code MUST follow literate programming principles: matches design intent without reading the full codebase - **Logical Separation**: Complex functions use block comments to separate and describe logical steps within the implementation -- **Full Symbol Documentation**: ALL symbols have comprehensive documentation - because reviewers and auditors must verify every implementation detail, not - just the public interface - access-level specifics (public, protected, - private, internal, etc.) vary by language; see the language-specific standard +- **Full Symbol Documentation**: ALL symbols have comprehensive documentation — + not just the public interface, because reviewers and auditors must verify every + implementation detail. Access-level specifics vary by language; see the language-specific standard. - **Clarity Over Cleverness**: Code should be immediately understandable by team members ## API Documentation @@ -79,13 +73,13 @@ interface correctly without reading the implementation: ## Universal Anti-Patterns -- **Skip Literate Coding**: Don't skip literate programming comments - they are required for maintainability -- **Ignore Compiler Warnings**: Don't ignore compiler warnings - they exist for quality enforcement +- **Skip Literate Coding**: Don't skip literate programming comments +- **Ignore Compiler Warnings**: Don't ignore compiler warnings - **Hidden Dependencies**: Don't create untestable code with hidden dependencies - **Hidden Functionality**: Don't implement functionality without requirement traceability because untraced functionality cannot be validated during audits - **Monolithic Functions**: Don't write monolithic functions with multiple responsibilities -- **Overcomplicated Solutions**: Don't make solutions more complex than necessary - favor simplicity and clarity +- **Overcomplicated Solutions**: Don't make solutions more complex than necessary - **Premature Optimization**: Don't optimize for performance before establishing correctness - **Copy-Paste Programming**: Don't duplicate logic - extract common functionality into reusable components - **Magic Numbers**: Don't use unexplained constants - either name them or add clear comments diff --git a/.github/standards/csharp-language.md b/.github/standards/csharp-language.md index 6df39cd..ec05a25 100644 --- a/.github/standards/csharp-language.md +++ b/.github/standards/csharp-language.md @@ -12,9 +12,6 @@ Read these standards first before applying this standard: # API Documentation and Literate Coding Example -The example below demonstrates good XmlDoc API documentation combined with -literate coding comments. - ```csharp /// /// Converts a raw sensor reading into a validated measurement ready for downstream consumers. diff --git a/.github/standards/csharp-testing.md b/.github/standards/csharp-testing.md index 181de02..1f93b72 100644 --- a/.github/standards/csharp-testing.md +++ b/.github/standards/csharp-testing.md @@ -66,8 +66,6 @@ These are non-obvious v3 behaviors that differ from v2 or common assumptions: # Quality Checks -Before submitting C# tests, verify: - - [ ] All tests follow AAA pattern with clear section comments - [ ] Test names follow hierarchical naming pattern above - [ ] Each test verifies single, specific behavior (no shared state between tests) diff --git a/.github/standards/design-documentation.md b/.github/standards/design-documentation.md index 635cb6d..c6f107f 100644 --- a/.github/standards/design-documentation.md +++ b/.github/standards/design-documentation.md @@ -8,6 +8,8 @@ globs: ["docs/design/**/*.md"] - **`technical-documentation.md`** - General technical documentation standards - **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS/Shared Package) +- **`sysml2-modeling.md`** - SysML2 model and view conventions feeding the Software Structure + section # Folder Structure @@ -28,7 +30,8 @@ docs/design/ └── {package-name}.md # heading depth ## ``` -Subsystems may nest recursively. Each file's heading depth equals its folder depth under `docs/design/`. +All sections in every file are mandatory; write "N/A - {justification}" rather than removing any. +Determine subsystem vs. unit classification from `docs/design/introduction.md` — folder depth does not determine classification. # introduction.md (MANDATORY) @@ -36,15 +39,15 @@ Must include: - **Purpose**: audience and compliance drivers - **Scope**: items covered and explicitly excluded (no test projects) -- **Software Structure**: text tree showing all Systems/Subsystems/Units/OTS/Shared items +- **Software Structure**: diagram(s) rendered from the SysML2 model under `docs/sysml2/`, + per `sysml2-modeling.md` — not a hand-maintained text tree - **Folder Layout**: text tree showing source folder structure - **Companion Artifact Structure**: parallel paths for requirements, design, verification, source, tests - **References** _(if applicable)_: external standards or specifications - only in `introduction.md` # System Design (MANDATORY) -Create `{system-name}.md` (`#` heading) and `{system-name}/` folder. All sections mandatory; -write "N/A - {justification}" rather than removing any section: +Create `{system-name}.md` (`#` heading) and `{system-name}/` folder: - **Architecture**: software items, relationships, and collaboration - **External Interfaces**: name, direction, format, constraints @@ -55,8 +58,7 @@ write "N/A - {justification}" rather than removing any section: # Subsystem Design (MANDATORY) -Place `{subsystem-name}.md` in the **parent** folder; create `{subsystem-name}/` for children. -All sections mandatory; write "N/A - {justification}" rather than removing any section: +Place `{subsystem-name}.md` in the **parent** folder; create `{subsystem-name}/` for children: - **Overview**: responsibility, boundaries, contained units - **Interfaces**: what it exposes and consumes @@ -64,34 +66,46 @@ All sections mandatory; write "N/A - {justification}" rather than removing any s # Unit Design (MANDATORY) -Place `{unit-name}.md` in the **parent** folder. All sections mandatory; -write "N/A - {justification}" rather than removing any section: +Place `{unit-name}.md` in the **parent** folder: - **Purpose**: single responsibility - **Data Model**: fields, properties, types, invariants (IEC 62304 §5.4.2) - **Key Methods**: name, purpose, algorithm, preconditions, postconditions, parameter types - **Error Handling**: detection and handling; what is propagated vs. handled locally -- **Interactions**: dependencies on other units/subsystems/OTS; who calls this unit +- **Dependencies**: other units, subsystems, OTS items, and shared packages used +- **Callers**: units or subsystems that call or consume this unit # OTS Integration Design (when OTS items exist) Create `docs/design/ots.md` (`#` heading) covering the overall OTS integration strategy. -For each OTS item, create `docs/design/ots/{ots-name}.md` (`##` heading) covering: -why chosen, which features/APIs used, integration patterns, version constraints. +For each OTS item, create `docs/design/ots/{ots-name}.md` (`##` heading) with sections: + +- **Purpose**: why chosen and what it provides to the local system +- **Features Used**: which specific features, APIs, or capabilities are consumed +- **Integration Pattern**: how it is consumed; initialization, configuration, disposal requirements # Shared Package Integration Design (when Shared Packages exist) Create `docs/design/shared.md` (`#` heading) covering the overall consumption strategy. -For each Shared Package, create `docs/design/shared/{package-name}.md` (`##` heading) covering: -which advertised features are consumed, integration pattern, configuration/initialization. +For each Shared Package, create `docs/design/shared/{package-name}.md` (`##` heading) with sections: + +- **Advertised Features Consumed**: which features the local system relies on +- **Integration Pattern**: how the package is referenced, initialized, and consumed +- **Assumptions**: any assumptions the local system makes about the package's behavior # Writing Guidelines - Use Mermaid diagrams to supplement (not replace) text - Use verbal cross-references ("see _Parser Design_") - not markdown hyperlinks (break in PDF) - Provide sufficient detail for formal code review +- Do not record version numbers in design documentation — they go stale with dependency updates and + are managed in SBOMs. Version numbers are pinned release versions (e.g., `1.2.3`, `v2.0.1`). + The following are **not** version numbers and are permitted: + - Language/platform standards: `netstandard2.0`, `net10.0`, `C++20`, `C# 12` (stable standard identifiers) + - Protocol standards: `TLS 1.3`, `HTTP/2` (stable specifications) + - Placeholders: `0.0.0` (signals "not yet assigned") # Quality Checks @@ -102,7 +116,7 @@ which advertised features are consumed, integration pattern, configuration/initi - [ ] System design includes all mandatory sections (Architecture, External Interfaces, Dependencies, Risk Control Measures, Data Flow, Design Constraints) - [ ] Subsystem design includes all mandatory sections (Overview, Interfaces, Design) -- [ ] Unit design includes all mandatory sections (Purpose, Data Model, Key Methods, Error Handling, Interactions) +- [ ] Unit design includes all mandatory sections (Purpose, Data Model, Key Methods, Error Handling, Dependencies, Callers) - [ ] Non-applicable mandatory sections contain "N/A - {justification}" - [ ] `docs/design/ots.md` and `docs/design/ots/{ots-name}.md` exist when OTS items are present - [ ] `docs/design/shared.md` and `docs/design/shared/{package-name}.md` exist when Shared Packages are present diff --git a/.github/standards/reqstream-usage.md b/.github/standards/reqstream-usage.md index 95d36a1..2371164 100644 --- a/.github/standards/reqstream-usage.md +++ b/.github/standards/reqstream-usage.md @@ -25,8 +25,8 @@ docs/reqstream/ │ ├── platform-requirements.yaml # Platform support requirements │ ├── {subsystem-name}.yaml # Subsystem requirements │ ├── {subsystem-name}/ # Subsystem folder (kebab-case); may nest recursively -│ │ ├── {child-subsystem}.yaml # Child subsystem requirements -│ │ ├── {child-subsystem}/ # Child subsystem folder +│ │ ├── {subsystem-name}.yaml # Child subsystem requirements +│ │ ├── {subsystem-name}/ # Child subsystem folder │ │ └── {unit-name}.yaml # Unit requirements │ └── {unit-name}.yaml # System-level unit requirements ├── ots/ # OTS items appear as a distinct section in reports @@ -35,62 +35,66 @@ docs/reqstream/ └── {package-name}.yaml # Requirements for Shared Package dependencies ``` -Local items have matching relative paths across `docs/reqstream/`, `docs/design/`, and -`docs/verification/`. OTS items appear in `docs/reqstream/ots/`, `docs/design/ots/`, and -`docs/verification/ots/`. Shared Packages appear in `docs/reqstream/shared/`, -`docs/design/shared/`, and `docs/verification/shared/`. +Local items have matching relative paths across `docs/reqstream/`, `docs/design/`, and `docs/verification/`: + +- Requirements: `{system-name}[/{subsystem-name}...]/{item-name}.yaml` +- Design: `{system-name}[/{subsystem-name}...]/{item-name}.md` +- Verification: `{system-name}[/{subsystem-name}...]/{item-name}.md` # Requirements File Format -```yaml -sections: - - title: Functional Requirements - requirements: - - id: System-Component-Feature # Used as-is in all reports - make it readable - title: The system shall perform the required function. - justification: | - Business rationale and any regulatory references. - # ReqStream extracts this field into the justifications report (--justifications) - children: # ReqStream validates this decomposition chain - - ChildSystem-Feature-Behavior # Downward links only (see requirements-principles.md) - tests: # ReqStream matches these by method name in test results - - TestMethodName - - windows@PlatformSpecificTest # Only test runs on Windows count as evidence -``` +Each file adds requirements at exactly one level of the hierarchy. The file spells out +its full ancestry as nested `{ItemName} Requirements` sections down to that level, then +places requirements there. ReqStream merges identical section title paths across included +files automatically. Always determine item classification from `docs/design/introduction.md` - +folder depth does not determine whether an item is a subsystem or unit. -# OTS Software Requirements +Valid section nestings (names in `{braces}` are placeholders): + +```text +{SystemName} Requirements # system-level requirements +├── {SubsystemName} Requirements # root subsystem requirements +│ ├── {SubsystemName} Requirements # nested subsystem (may recurse) +│ │ └── {UnitName} Requirements # unit under a nested subsystem +│ └── {UnitName} Requirements # unit under a root subsystem +└── {UnitName} Requirements # unit directly under the system +OTS Software Requirements # OTS root section (fixed title) +└── {OtsName} Requirements # requirements for one OTS item +Shared Package Requirements # shared package root section (fixed title) +└── {PackageName} Requirements # requirements for one shared package +``` -Use nested sections in `docs/reqstream/ots/` because ReqStream renders the `ots/` -subtree as a distinct section in generated reports, separate from local -system requirements: +Each file implements one path through this tree: ```yaml sections: - - title: OTS Software Requirements + - title: '{SystemName} Requirements' sections: - - title: System.Text.Json + - title: '{SubsystemName} Requirements' requirements: - - id: SystemTextJson-Core-ReadJson - title: System.Text.Json shall be able to read JSON files. - tests: - - JsonReaderTests.TestReadValidJson + - id: System-Subsystem-Feature # Used as-is in all reports - make it readable + title: The subsystem shall perform the required function. + justification: | # ReqStream extracts this into the justifications report (--justifications) + Business rationale and any regulatory references. + tags: # Optional: categorize for filtering with --filter + - security + children: # Optional: ReqStream validates this decomposition chain + - System-Subsystem-Unit-Feat # Downward links only (see requirements-principles.md) + tests: # ReqStream matches these by method name in test results + - TestMethodName + - windows@PlatformSpecificTest # Only test runs on Windows count as evidence ``` -# Shared Package Requirements +# Tags (OPTIONAL) -Use nested sections in `docs/reqstream/shared/` - ReqStream renders the `shared/` -subtree as a distinct section in reports, separate from local and OTS requirements: +Tags are free-form - no mandatory vocabulary. Common tags: `security`, `safety`, `performance`, +`compliance`, `reliability`, `critical`. Use `--filter` to selectively export or enforce subsets +(OR logic across comma-separated tags): -```yaml -sections: - - title: Shared Package Requirements - sections: - - title: MyOrg.SharedLibrary - requirements: - - id: SharedLibrary-Core-ParseConfig - title: MyOrg.SharedLibrary shall parse configuration files. - tests: - - SharedLibraryIntegrationTests.TestParseValidConfig +```bash +dotnet reqstream --requirements requirements.yaml \ + --filter security,critical \ + --report docs/requirements_doc/generated/security_requirements.md ``` # Semantic IDs (MANDATORY) @@ -145,13 +149,9 @@ dotnet reqstream --requirements requirements.yaml \ Before submitting requirements, verify: -- [ ] All requirements have semantic IDs (`System-Section-Feature` pattern) -- [ ] Every requirement links to at least one passing test +- [ ] All requirements have semantic IDs (`System-Component-Feature` pattern) +- [ ] Every requirement has a justification explaining business/regulatory need +- [ ] Every requirement links to at least one test - [ ] Platform-specific requirements use source filters (`platform@TestName`) -- [ ] Comprehensive justification explains business/regulatory need -- [ ] Files organized under `docs/reqstream/` following the folder structure pattern above -- [ ] All documentation folders use kebab-case names matching source code structure -- [ ] OTS requirements placed in `ots/` subfolder -- [ ] Shared Package requirements placed in `shared/` subfolder -- [ ] Valid YAML syntax passes yamllint validation -- [ ] Test result formats compatible (TRX, JUnit XML) +- [ ] All files and folders use kebab-case names matching source code structure +- [ ] All files are organized under `docs/reqstream/` following the folder structure above diff --git a/.github/standards/reviewmark-usage.md b/.github/standards/reviewmark-usage.md index 990d707..bc73da6 100644 --- a/.github/standards/reviewmark-usage.md +++ b/.github/standards/reviewmark-usage.md @@ -21,101 +21,122 @@ review, organizes them into review-sets, and generates review plans and reports. - **Lint Configuration**: `dotnet reviewmark --lint` - **Elaborate Review-Set**: `dotnet reviewmark --elaborate {review-set}` - **Generate Plan**: `dotnet reviewmark --plan docs/code_review_plan/generated/plan.md --enforce` - -> **Note**: `--enforce` causes the plan to fail with a non-zero exit code if any repository -> files are not covered by a review-set. Uncovered files indicate a gap in review-set -> configuration that should be addressed. + (exits non-zero if any files are uncovered) ## Repository Structure -Required repository items for ReviewMark operation: - - `.reviewmark.yaml` - Configuration for review-sets, file-patterns, and review evidence-source. -- `docs/code_review_plan/generated/` - Generated review plan (build output, do not edit) -- `docs/code_review_report/generated/` - Generated review report (build output, do not edit) # Review Definition Structure Configure reviews in `.reviewmark.yaml` at repository root: ```yaml -# Patterns identifying all files that require review needs-review: - # Include source code (adjust file extensions for your repo) - - "**/*.cs" # C# source files - - "**/*.cpp" # C++ source files - - "**/*.hpp" # C++ header files - - "!**/bin/**" # Generated source in build outputs - - "!**/obj/**" # Generated source in build intermediates - - # Include requirement files - - "requirements.yaml" # Root requirements file - - "docs/reqstream/**/*.yaml" # Requirements files - - # Include critical documentation files - - "README.md" # Root level README - - "docs/user_guide/**/*.md" # User guide - - "docs/design/**/*.md" # Design documentation - - "docs/verification/**/*.md" # Verification design documentation - -# Source of review evidence + - "**/*.cs" + - "**/*.cpp" + - "**/*.hpp" + - "!**/bin/**" + - "!**/obj/**" + - "requirements.yaml" + - "docs/reqstream/**/*.yaml" + - "docs/sysml2/**/*.sysml" + - "README.md" + - "docs/user_guide/**/*.md" + - "docs/design/**/*.md" + - "docs/verification/**/*.md" + evidence-source: type: none -# Review-sets (each focuses on a single compliance question) +context: + - docs/design/introduction.md + reviews: - id: Purpose - title: Review of user-facing capabilities and system promises + title: Review that README and User Guide are Coherent and Complete paths: - "README.md" - "docs/user_guide/**/*.md" - - "docs/reqstream/{system-name}.yaml" + - id: Decomposition + title: Review that {SystemName} Decomposition Addresses the Stated Purpose + context: + - "README.md" + - "docs/user_guide/**/*.md" + paths: + - "requirements.yaml" - "docs/design/introduction.md" - - "docs/design/{system-name}.md" ``` -# Review-Set Design Principles +For a complete annotated example with template directives, see `.reviewmark.yaml` in the +reference template (`{template-url}/.reviewmark.yaml` per `AGENTS.md`). -When constructing review-sets, follow these principles to maintain manageable scope and effective compliance evidence: +# Review-Set Design Principles - **Hierarchical Scope**: Higher-level reviews exclude lower-level implementation details, relying instead on design documents to describe what components they use. System reviews exclude subsystem/unit details, subsystem reviews exclude unit source code, only unit reviews include actual implementation. - **Single Focus**: Each review-set proves one specific compliance question (user promises, system architecture, design consistency, etc.) +- **Parent Context**: Unit and subsystem reviews include parent design and requirements as + context so reviewers understand the intended role and scope; see the Context Files section. - **Context Management**: Keep file counts manageable to prevent context overflow while maintaining complete coverage through the hierarchy -# Review-Set Organization +# Context Files -Organize review-sets using these standard patterns to ensure comprehensive coverage -while keeping each review manageable in scope: +Context files are shown to reviewers for orientation but not fingerprinted. Add a top-level +`context:` key for global context (every reviewer) and a per-review-set `context:` between +`title:` and `paths:` for review-specific context. Always include `docs/design/introduction.md` +as global context. -**Naming conventions**: See `software-items.md` - kebab-case placeholders -(e.g., `{system-name}`) are always kebab-case; cased placeholders -(e.g., `{SystemName}`) follow your language's convention. +| Review Type | Context to add | +| :---------- | :------------- | +| `Decomposition` | `README.md`, `docs/user_guide/**/*.md` | +| `{SystemName}-Architecture` | `README.md`, `docs/user_guide/**/*.md` | +| `{SystemName}-Design` | `docs/reqstream/{system-name}.yaml` | +| `{SystemName}-Verification` | `docs/reqstream/{system-name}.yaml` | +| `{SystemName}-AllRequirements` | Parent system design doc + `docs/reqstream/{system-name}.yaml` | +| `{SystemName}-{UnitName}` (direct unit) | Parent system design doc + parent system requirements | +| `{SystemName}-{SubsystemName}` (subsystem) | Parent system design doc + parent system requirements | +| `{SystemName}-{SubsystemName}-{UnitName}` (unit under subsystem) | System + subsystem design docs + requirements | -## `Purpose` Review (only one per repository) +# Review-Set Organization + +**Naming conventions**: Placeholders in documentation, requirements, design, and +verification file paths are kebab-case (e.g., `{system-name}`). Placeholders in +source and test file paths may use the casing conventional for the project's +source language or repository layout (e.g., `{SystemName}`). Review-set name +placeholders are always PascalCase (e.g., `{SystemName}`). -Reviews user-facing capabilities and system promises: +## `Purpose` Review (only one per repository) -- **Purpose**: Proves that the systems provide the capabilities the user is being told about -- **Title**: "Review that Advertised Features Match System Design" -- **Scope**: Excludes subsystem and unit files, relying on system-level design documents - to describe what subsystems and units they use +- **Purpose**: Proves that the user-facing docs are coherent and complete — the north-star for the hierarchy +- **Title**: "Review that README and User Guide are Coherent and Complete" +- **ID**: `Purpose` (no system prefix — one per repository) +- **Scope**: README and user_guide only; no requirements or design files - **File Path Patterns**: - README: `README.md` - User guide: `docs/user_guide/**/*.md` - - System requirements: `docs/reqstream/{system-name}.yaml` - - Design introduction: `docs/design/introduction.md` - - System design: `docs/design/{system-name}.md` -## `{System}-Architecture` Review (one per system) +## `Decomposition` Review (only one per repository) -Reviews system architecture and operational validation: +- **Purpose**: Proves that the software items tree breakdown logically addresses the user-facing + promise; the structural mirror of the decomposition decision +- **Title**: "Review that {SystemName} Decomposition Addresses the Stated Purpose" +- **ID**: `Decomposition` (no system prefix — one per repository) +- **Scope**: introduction.md (the decomposition narrative), the SysML2 model (the + authoritative structural tree), and requirements.yaml; no system-level detail +- **File Path Patterns**: + - Root requirements: `requirements.yaml` + - Design introduction: `docs/design/introduction.md` + - SysML2 model: `docs/sysml2/**/*.sysml` +- **Context Files**: `README.md`, `docs/user_guide/**/*.md` + +## `{SystemName}-Architecture` Review (one per system) - **Purpose**: Proves that the system is designed and tested to satisfy its requirements -- **Title**: "Review that {System} Architecture Satisfies Requirements" +- **Title**: "Review that {SystemName} Architecture Satisfies Requirements" - **Scope**: Excludes subsystem and unit files, relying on system-level design to describe what subsystems and units it uses - **File Path Patterns**: @@ -125,16 +146,15 @@ Reviews system architecture and operational validation: - Verification introduction: `docs/verification/introduction.md` - System verification design: `docs/verification/{system-name}.md` - System integration tests: `test/{SystemName}.Tests/{SystemName}Tests.{ext}` +- **Context Files**: `README.md`, `docs/user_guide/**/*.md` -## `{System}-Design` Review (one per system) - -Reviews architectural and design consistency: +## `{SystemName}-Design` Review (one per system) - **Purpose**: Proves the system design is consistent and complete -- **Title**: "Review that {System} Design is Consistent and Complete" +- **Title**: "Review that {SystemName} Design is Consistent and Complete" - **Scope**: Only brings in top-level requirements and relies on brevity of design documentation +- **Context Files**: `docs/reqstream/{system-name}.yaml` - **File Path Patterns**: - - System requirements: `docs/reqstream/{system-name}.yaml` - Platform requirements: `docs/reqstream/{system-name}/platform-requirements.yaml` - Design introduction: `docs/design/introduction.md` - System design: `docs/design/{system-name}.md` @@ -142,65 +162,59 @@ Reviews architectural and design consistency: - OTS overview: `docs/design/ots.md` _(only if OTS items exist)_ - Shared Package overview: `docs/design/shared.md` _(only if Shared Package items exist)_ -## `{System}-Verification` Review (one per system) - -Reviews verification completeness and consistency: +## `{SystemName}-Verification` Review (one per system) - **Purpose**: Proves the system verification design is consistent and covers all requirements -- **Title**: "Review that {System} Verification is Consistent and Complete" +- **Title**: "Review that {SystemName} Verification is Consistent and Complete" - **Scope**: Only brings in top-level requirements and all verification docs for the system +- **Context Files**: `docs/reqstream/{system-name}.yaml` - **File Path Patterns**: - - System requirements: `docs/reqstream/{system-name}.yaml` - Verification introduction: `docs/verification/introduction.md` - System verification: `docs/verification/{system-name}.md` - System verification files: `docs/verification/{system-name}/**/*.md` - OTS overview: `docs/verification/ots.md` _(only if OTS items exist)_ - Shared Package overview: `docs/verification/shared.md` _(only if Shared Package items exist)_ -## `{System}-AllRequirements` Review (one per system) - -Reviews requirements quality and traceability: +## `{SystemName}-AllRequirements` Review (one per system) - **Purpose**: Proves the requirements are consistent and complete -- **Title**: "Review that All {System} Requirements are Complete" +- **Title**: "Review that All {SystemName} Requirements are Complete" - **Scope**: Only brings in requirements files to keep review manageable - **File Path Patterns**: - - Root requirements: `requirements.yaml` - - System requirements: `docs/reqstream/{system-name}.yaml` - Subsystem/unit requirements: `docs/reqstream/{system-name}/**/*.yaml` +- **Context Files**: `docs/design/{system-name}.md`, `docs/reqstream/{system-name}.yaml` -## `{System}-{Subsystem[-Child...]}` Review (one per subsystem at any depth) - -Reviews subsystem architecture and interfaces: +## `{SystemName}-{SubsystemName}[-{SubsystemName}...]` Review (one per subsystem at any depth) - **Purpose**: Proves that the subsystem is designed and tested to satisfy its requirements -- **Title**: "Review that {System} {Subsystem} Satisfies Subsystem Requirements" +- **Title**: "Review that {SystemName} {SubsystemName} Satisfies Subsystem Requirements" - **Scope**: Excludes units under the subsystem, relying on subsystem design to describe what units it uses - **File Path Patterns**: - - Requirements: `docs/reqstream/{system-name}/.../{subsystem-name}.yaml` - - Design: `docs/design/{system-name}/.../{subsystem-name}.md` - - Verification design: `docs/verification/{system-name}/.../{subsystem-name}.md` - - Tests: `test/{SystemName}.Tests/.../{SubsystemName}/{SubsystemName}Tests.{ext}` + - Requirements: `docs/reqstream/{system-name}[/{subsystem-name}...]/{subsystem-name}.yaml` + - Design: `docs/design/{system-name}[/{subsystem-name}...]/{subsystem-name}.md` + - Verification design: `docs/verification/{system-name}[/{subsystem-name}...]/{subsystem-name}.md` + - Tests: `test/{SystemName}.Tests[/{SubsystemName}...]/{SubsystemName}Tests.{ext}` +- **Context Files**: `docs/design/{system-name}.md`, `docs/reqstream/{system-name}.yaml` -## `{System}-{Subsystem[-Child...]}-{Unit}` Review (one per unit) - -Reviews individual software unit implementation: +## `{SystemName}-{SubsystemName}[-{SubsystemName}...]-{UnitName}` Review (one per unit) - **Purpose**: Proves the unit is designed, implemented, and tested to satisfy its requirements -- **Title**: "Review that {System} {Subsystem} {Unit} Implementation is Correct" +- **Title**: "Review that {SystemName} {SubsystemName} {UnitName} Implementation is Correct" - **Scope**: Complete unit review including all artifacts - **File Path Patterns**: - - Requirements: `docs/reqstream/{system-name}/.../{unit-name}.yaml` - - Design: `docs/design/{system-name}/.../{unit-name}.md` - - Verification design: `docs/verification/{system-name}/.../{unit-name}.md` - - Source: `src/{SystemName}/.../{UnitName}.{ext}` - - Tests: `test/{SystemName}.Tests/.../{UnitName}Tests.{ext}` + - Requirements: `docs/reqstream/{system-name}[/{subsystem-name}...]/{unit-name}.yaml` + - Design: `docs/design/{system-name}[/{subsystem-name}...]/{unit-name}.md` + - Verification design: `docs/verification/{system-name}[/{subsystem-name}...]/{unit-name}.md` + - Source (C# example): `src/{SystemName}[/{SubsystemName}...]/{UnitName}.cs` + - Tests (C# example): `test/{SystemName}.Tests[/{SubsystemName}...]/{UnitName}Tests.cs` + - Source (snake_case C++ example): `src/{system_name}[/{subsystem_name}...]/{unit_name}.cpp` + - Tests (snake_case C++ example): `test/{system_name}_tests[/{subsystem_name}...]/{unit_name}_tests.cpp` +- **Context Files**: Parent system design + requirements; add subsystem design + + requirements for each subsystem level above the unit. ## `OTS-{OtsName}` Review (one per OTS item) -Reviews OTS item integration design, requirements, and verification evidence: - - **Purpose**: Proves that the OTS item provides the required functionality and is correctly integrated - **Title**: "Review that {OtsName} Provides Required Functionality" - **Scope**: No local source code; review covers integration design, requirements, and verification evidence @@ -208,12 +222,11 @@ Reviews OTS item integration design, requirements, and verification evidence: - OTS requirements: `docs/reqstream/ots/{ots-name}.yaml` - OTS integration design: `docs/design/ots/{ots-name}.md` - OTS verification: `docs/verification/ots/{ots-name}.md` - - Tests (if applicable): `test/{OtsSoftwareTests}/...` (cased per language) + - Tests (if applicable): `test/OtsSoftwareTests/...` (C#) or `test/ots_software_tests/...` + (Python/other) — fixed repo-level name, no system prefix ## `Shared-{PackageName}` Review (one per Shared Package) -Reviews Shared Package integration design, requirements, and verification evidence: - - **Purpose**: Proves that the Shared Package provides the required advertised features and is correctly integrated - **Title**: "Review that {PackageName} Provides Required Features" - **Scope**: No local source code; review covers integration design, requirements, and verification evidence @@ -227,21 +240,9 @@ extensions (`.cs`, `.cpp`/`.hpp`, `.py`, etc.). Adapt to your repository's langu # Quality Checks -Before submitting ReviewMark configuration, verify: - - [ ] `.reviewmark.yaml` exists at repository root with proper structure - [ ] Review-set organization follows the standard hierarchy patterns -- [ ] Purpose review-set includes README.md, user guide, system requirements, design introduction, and system design files -- [ ] System-level reviews follow hierarchical scope principle (exclude subsystem/unit details) -- [ ] Subsystem reviews follow hierarchical scope principle (exclude unit source code) -- [ ] Only unit reviews include actual source code files -- [ ] Architecture review-sets include system verification design alongside system design -- [ ] Design review-sets include all system design files -- [ ] Verification review-sets include all system verification files -- [ ] Subsystem review-sets include subsystem verification design -- [ ] Unit review-sets include unit verification design -- [ ] OTS review-sets include OTS requirements, integration design, and verification evidence -- [ ] Shared Package review-sets include Shared Package requirements, integration design, and verification evidence - [ ] Each review-set focuses on a single compliance question (single focus principle) - [ ] File patterns use correct glob syntax and match intended files - [ ] Review-set file counts remain manageable (context management principle) +- [ ] Context configured per the Context Files section diff --git a/.github/standards/software-items.md b/.github/standards/software-items.md index 328a08e..3514228 100644 --- a/.github/standards/software-items.md +++ b/.github/standards/software-items.md @@ -3,12 +3,6 @@ name: Software Items description: Follow these standards when categorizing software components. --- -# Software Items Definition Standards - -This document defines standards for categorizing software items within -Continuous Compliance environments because proper categorization determines -requirements management approach, testing strategy, and review scope. - # Software Item Categories Categorize all software into six primary groups: @@ -40,17 +34,28 @@ Categorize all software into six primary groups: # Naming Conventions in File Path Patterns -Two placeholder styles appear in path patterns across these standards: +Three placeholder forms appear in path patterns across these standards: -- **Kebab-case** (`{system-name}`, `{unit-name}`): always kebab-case - - used in documentation and requirements paths -- **Cased** (`{SystemName}`, `{UnitName}`): follow your language's convention - - `PascalCase` for C#/Java, `snake_case` for C++/Python - - used in source and test file paths +- **Kebab-case** (`{system-name}`, `{unit-name}`): always kebab-case — + documentation and requirements file paths +- **PascalCase IDs** (`{SystemName}`, `{UnitName}`): always PascalCase — + requirements IDs, ReviewMark IDs, and other documentation identifiers +- **Language-cased** (`{SystemName}` or `{system_name}`): follow your language's + convention — `PascalCase` for C#/Java, `snake_case` for C++/Python — + source and test file/folder names -# Categorization Guidelines +## Nesting Depth Notation + +Subsystems nest to any depth. Patterns use bracket-ellipsis to express this without +enumerating levels — `[/{subsystem-name}...]` in paths, `[-{SubsystemName}...]` in +dash-separated IDs. Examples covering all three forms: -Choose the appropriate category based on scope and testability: +- `{SystemName}[-{SubsystemName}...]-{UnitName}-Feature` (PascalCase ID) +- `docs/design/{system-name}[/{subsystem-name}...]/{unit-name}.md` (kebab-case doc path) +- `src/{SystemName}[/{SubsystemName}...]/{UnitName}.cs` (C# source path) +- `src/{system_name}[/{subsystem_name}...]/{unit_name}.cpp` (C++/Python source path) + +# Categorization Guidelines ## Software Package @@ -89,13 +94,15 @@ Choose the appropriate category based on scope and testability: - Examples: System.Text.Json, Entity Framework, third-party APIs - **Artifact locations** (OTS items have no internal design documentation): - Requirements: `docs/reqstream/ots/{ots-name}.yaml` - - Design: `docs/design/ots/{ots-name}.md` (integration/usage design - how the local system uses this item) + - Design: `docs/design/ots/{ots-name}.md` (integration/usage design) - Verification: `docs/verification/ots/{ots-name}.md` - These folders sit parallel to system folders (not inside any system folder) - System design documentation records which OTS items each system depends on - **OTS test project**: If no other verification evidence is available (e.g., vendor test results, - published compliance reports), a dedicated test project (`OtsSoftwareTests` / `ots_software_tests`, - cased per language) holds OTS integration tests - one test file per OTS item requiring tests. + published compliance reports), a dedicated test project holds OTS integration tests - one test + file per OTS item requiring tests. OTS items are repo-level (not per-system), so the project + uses a fixed repo-level name: `test/OtsSoftwareTests/` (C#) or `test/ots_software_tests/` + (Python/other) — never prefixed with a system or project name. ## Shared Package @@ -107,10 +114,9 @@ Choose the appropriate category based on scope and testability: downstream integration tests that transitively prove the advertised features are functional - **Artifact locations** (no internal design documentation in the consuming repository): - Requirements: `docs/reqstream/shared/{package-name}.yaml` - - Design: `docs/design/shared/{package-name}.md` (integration/usage design - which features are consumed and how) + - Design: `docs/design/shared/{package-name}.md` (integration/usage design) - Verification: `docs/verification/shared/{package-name}.md` - These folders sit parallel to system and OTS folders -- System design documentation records which Shared Packages each system depends on # Software Item Artifact Model @@ -124,3 +130,8 @@ the item is correct, well-designed, and proven to work: - **Verification Design** - HOW the requirements will be tested (applies to all item types) - **Source code** - The implementation of the design (local units only; not applicable to OTS or Shared Package) - **Tests** - PROOF the item does WHAT it is required to do (applies to all item types) + +Where the repository models its software structure in SysML2, each item's part def carries +`comment` metadata pointing at these same artifact locations (`sourceRef`/`testRef`/ +`designRef`/`verificationRef`/`reqRef`) so agents can query them directly — see +`sysml2-modeling.md`. diff --git a/.github/standards/sysml2-modeling.md b/.github/standards/sysml2-modeling.md new file mode 100644 index 0000000..c5558e1 --- /dev/null +++ b/.github/standards/sysml2-modeling.md @@ -0,0 +1,232 @@ +--- +name: SysML2 Modeling +description: Follow these standards when creating or modifying the SysML2 architecture + model, its rendered views, or the software structure it feeds into design documentation. +globs: ["docs/sysml2/**/*.sysml"] +--- + +# SysML2 Modeling Standard + +## Required Standards + +Read these standards first before applying this standard: + +- **`software-items.md`** - Software categorization (System/Subsystem/Unit/OTS/Shared Package) +- **`design-documentation.md`** - Design document structure that embeds the diagrams this + standard produces + +## Purpose + +The repository's software structure is modeled in SysML2 under `docs/sysml2/` rather than +hand-maintained as prose or an ASCII tree. The model is the authoritative, machine-queryable +source of structure; rendered diagrams and `docs/design/introduction.md`'s narrative are +generated/derived artifacts. AI agents should query the model (see `sysml2tools-query` +skill) before deep-diving into source code to understand what a piece of the system is, +why it exists, and what it depends on. + +## Repository Structure + +```text +docs/sysml2/ +├── model/ +│ ├── {system-name}.sysml # system-level part def only (no subsystems/units inline) +│ ├── {system-name}/ +│ │ ├── {subsystem-name}.sysml # subsystem part def; nests further for sub-subsystems +│ │ └── {subsystem-name}/ +│ │ └── {unit-name}.sysml # one file per unit +│ ├── ots.sysml # OTS dependency parts (optional; if OTS items exist) +│ └── shared.sysml # Shared Package parts (optional; if Shared Packages exist) +└── views/ + └── design-views.sysml # named view usages rendered for the design document +``` + +`docs/sysml2/model/` mirrors the same folder shape as `docs/design/`, `docs/reqstream/`, and +`docs/verification/` — one `.sysml` file per System/Subsystem/Unit, at the same nesting depth +as that item's companion design/requirements/verification files. This keeps each file small +and keeps PRs that touch one unit's model from producing unrelated diffs in unrelated units. +`docs/sysml2/views/` is a sibling of `model/`, not nested inside it, so a single +`docs/sysml2/model/**/*.sysml` glob never needs to exclude view files (`sysml2tools` has no +exclude-glob syntax). + +There is no separate "stable entry point" file — a repository may contain multiple systems, +so no single alias name would generalize. Agents discover the system(s) present by running +`query list --kind "part def"` or `query find` (see the `sysml2tools-query` skill) over +`docs/sysml2/model/**/*.sysml`, not by assuming a fixed name. + +Always pass the full set of `.sysml` files (or an equivalent glob) to every `sysml2tools` +invocation — the model spans multiple files and cross-references between them; files sharing +the same `package Name { ... }` merge into one namespace automatically with no `import` +required, as long as all files are passed together. As of `sysml2tools` 0.1.0-beta.5, +`lint` and `render` both expand `*`/`**` glob patterns internally (recursing correctly into +subdirectories), so pass a single **quoted** glob string (e.g. `'docs/sysml2/**/*.sysml'`) +and let the tool resolve it — do not rely on the shell to expand it, and do not quote-strip +the pattern before it reaches the tool. Quoting keeps the behavior identical across +PowerShell and bash, since neither shell needs to (or reliably does) expand `**` itself. + +## Model Content + +- `{system-name}.sysml` defines one `part def` for the System only, with `part` usages + referencing its direct Subsystems/Units (defined in their own files, see below). +- Each `{subsystem-name}.sysml` / `{unit-name}.sysml` defines exactly one `part def`, with a + `doc /* ... */` comment stating its purpose — mirroring what would otherwise be written as + prose in `docs/design/introduction.md`'s Software Structure section. Subsystem files add + `part` usages for their own direct children (nested subsystems or units), matching the + Software Item Hierarchy in `software-items.md`. +- `ots.sysml` / `shared.sysml` define one `part def` per OTS item / Shared Package, referenced + as `part` usages from the system(s) that depend on them. + +## Artifact-Location Comments + +Every System/Subsystem/Unit `part def` records where its companion artifacts live, as named +`comment` elements — not `attribute`s (attribute values are not surfaced by `sysml2tools query +describe`, even in JSON output; comments are). Comments have zero effect on rendered diagrams +(verified: a part def with several comments attached renders with no extra nodes or size +change) and are fully returned by `query describe`, in declaration order, as +`Comment: ...` summary lines — this is how an agent gets every artifact location for a unit +in a single query, without grepping the source tree. + +Use these comment names, in this order, when applicable to the item: + +```sysml +part def {UnitName} { + doc /* One-line purpose statement. */ + + comment sourceRef /* Source: {path to the unit's source file} */ + comment testRef /* Test: {path to the unit's test file} */ + comment designRef /* Design: {path to the unit's design doc} */ + comment verificationRef /* Verification: {path to the unit's verification doc} */ + comment reqRef /* Requirements: {path to the unit's reqstream file} */ +} +``` + +Not every item has all five: + +- **Systems and Subsystems** have no `sourceRef` (no single source file represents a whole + system or subsystem) — use `testRef` for the subsystem-level test file when one exists. +- **Units without a dedicated companion doc** (e.g. small data-model types documented inline + within their subsystem's design doc rather than in their own file) point `designRef` / + `verificationRef` / `reqRef` at the subsystem-level doc, with a short parenthetical note, + e.g. `/* Design: docs/design/{system}/{subsystem}.md (documented as a supporting value type) */`. +- **Units without a dedicated test file** (exercised only indirectly through another unit's + tests) omit `testRef` entirely rather than pointing at an unrelated test file. +- **OTS items** have no `sourceRef`/`testRef` (no local integration code); use `designRef` + (when the optional design doc exists) / `verificationRef` / `reqRef` only. + +This is prose-searchable metadata only — it is not a substitute for the authoritative +requirements/design/verification artifacts, and it does not replace ReqStream as the +requirements source of truth. (This repository currently keeps requirements traceability in +ReqStream; a future migration to native SysML2 `requirement`/`satisfy` modeling is a separate, +not-yet-scheduled change.) + +## Views (`docs/sysml2/views/design-views.sysml`) + +Views control what gets rendered into diagrams for the design document. Use named `view` +**usages** (not `view def` **definitions**) — `expose` is only valid inside a usage: + +```sysml +package {SystemName} { + view SoftwareStructureView { + expose {SystemName}; // whole package: system + subsystems + units + } + + view {SystemName}View { + expose {SystemName}System; // system def only, not expanded into subsystems + } + + view {SubsystemName}View { + expose {SubsystemName}; // one subsystem def only + } +} +``` + +**Critical distinction** (do not confuse these — this cost significant trial-and-error to +discover): `expose ;` is what scopes a rendered diagram's content (the union of the +named element's containment subtree). `render ;` selects a rendering *style* (e.g. +`asInterconnectionDiagram`) — it has **no effect on scope**. `expose` is only legal inside a named +`view Name { ... }` **usage**; it is a syntax/semantic error inside a `view def Name { ... }` +**definition**. `expose` targets must be `::`-qualified or locally-resolvable names, not +dotted member-access chains (`expose foo.bar;` is invalid — use `expose Bar;`). + +**Naming convention** — one `View` suffix per rendered diagram, no `System`/`Subsystem` +suffix duplication: + +- `SoftwareStructureView` - full detail: every system, subsystem, and unit in one diagram +- `{SystemName}View` - one per system, direct members only (not expanded into subsystems) +- `{SubsystemName}View` - one per subsystem (at any nesting depth), that subsystem's own + members only + +When a repository has multiple systems, `SoftwareStructureView` may expose each system's +package individually (`expose {SystemNameA}; expose {SystemNameB};`) or the whole workspace, +whichever produces a single coherent overview diagram. + +## Diagram Embedding + +Render with a single quoted recursive glob for the model tree, plus the views file +(`sysml2tools` 0.1.0-beta.5+ expands and recurses `**` internally, so no shell-side globbing +or explicit per-file listing is needed): + +```pwsh +dotnet sysml2tools render ` + --output docs/design/generated --format svg ` + 'docs/sysml2/model/**/*.sysml' ` + 'docs/sysml2/views/design-views.sysml' +``` + +With multiple views declared and no `--view` flag, `sysml2tools` renders every declared view +in one invocation, one file per view, using each view's own name as the filename +(`{ViewName}.svg`) — no post-render rename step is needed. + +Embed diagrams in `docs/design/` per this rule: every design doc for an item embeds the +diagram for the narrowest view that still shows that item's own immediate structure — + +- `docs/design/introduction.md` — `SoftwareStructureView.svg` (the full-detail overview) +- `docs/design/{system-name}.md` and every unit doc for a unit directly under the system + (no subsystem parent) — `{SystemName}View.svg` +- `docs/design/{system-name}/{subsystem-name}.md` and every unit doc nested under that + subsystem (at any depth) — `{SubsystemName}View.svg` + +Place the image directly under the file's top-level heading, before its first prose +subsection, e.g. `![{Name} Structure]({ViewName}.svg)`. + +## Build and Lint Integration + +- `sysml2tools lint` belongs in `lint.ps1`'s compliance-tools section, **not** as a separate + step in the CI design-document job — `lint.ps1` gates every later job (including document + generation) transitively, so linting the model there is both earlier and non-duplicated. + Pass a single quoted recursive glob, e.g. `dotnet sysml2tools lint 'docs/sysml2/**/*.sysml'` + — `sysml2tools` (0.1.0-beta.5+) expands and recurses this itself, so no + `Get-ChildItem`/shell-side globbing is needed. +- The CI design-document job renders the views (see the render command above) before running + Pandoc, so generated SVGs exist before HTML generation. Rename/stable-filename workarounds + are unnecessary since view names are stable by definition. Pass the model and views globs as + separate quoted arguments (e.g. `'docs/sysml2/model/**/*.sysml' 'docs/sysml2/views/design-views.sysml'`) + — plain PowerShell (the default shell) is sufficient; no `shell: bash`/`shopt -s globstar` + workaround is needed. +- Add `sysml2tools` to `.config/dotnet-tools.json` (`demaconsulting.sysml2tools.tool`) and to + `.versionmark.yaml`'s captured tool list. + +## Fallback + +If the model is stale, doesn't yet cover an item, or a query returns nothing useful, fall +back to `grep`/`glob`/reading source directly. When work adds or changes a Unit/Subsystem, +update the corresponding `.sysml` model file in the same change — this mirrors the existing +requirement to keep `docs/design/*.md` and `docs/reqstream/*.yaml` companion artifacts in +sync. + +## Quality Checks + +- [ ] Every System/Subsystem/Unit in `docs/design/introduction.md` has a matching `part def` + in its own file under `docs/sysml2/model/`, at the same folder depth as its companion + design/reqstream/verification files, with a purpose `doc` comment +- [ ] Every Unit-level `part def` has `sourceRef`/`testRef`/`designRef`/`verificationRef`/ + `reqRef` comments where applicable (see Artifact-Location Comments for documented + exceptions); System/Subsystem `part def`s have `testRef`/`designRef`/`verificationRef`/ + `reqRef` but no `sourceRef` +- [ ] `docs/sysml2/views/design-views.sysml` uses `view Name { expose ...; }` usages, not + `view def` definitions +- [ ] View names follow the `SoftwareStructureView` / `{SystemName}View` / + `{SubsystemName}View` convention +- [ ] `sysml2tools lint` passes on all `.sysml` files +- [ ] Rendered diagrams are embedded in every design doc per the Diagram Embedding rule +- [ ] `sysml2tools` is present in `lint.ps1`, `.config/dotnet-tools.json`, and + `.versionmark.yaml` diff --git a/.github/standards/technical-documentation.md b/.github/standards/technical-documentation.md index 0dc4455..23893bd 100644 --- a/.github/standards/technical-documentation.md +++ b/.github/standards/technical-documentation.md @@ -6,9 +6,6 @@ globs: ["docs/**/*.md", "README.md", "!docs/**/generated/**"] # Technical Documentation Standards -This document defines standards for technical documentation within Continuous -Compliance environments. - # Core Principles Technical documentation serves as compliance evidence and must be structured @@ -40,8 +37,7 @@ docs/{collection}/ Without `title.txt` and `definition.yaml` the pipeline cannot generate the document. When creating a new document collection, create these three files together and use -the existing collections under `docs/` as templates - they share a consistent -structure across all collections. +the existing collections under `docs/` as templates. The `generated/` folder is **never committed** to the repository - it is created locally and in CI by the build pipeline. Do not flag its absence as a conformance @@ -85,8 +81,7 @@ elsewhere causes duplicate sections in the compiled PDF. ## Document Ordering -List documents in logical reading order in Pandoc configuration because -readers need coherent information flow from general to specific topics. +List documents in logical reading order in `definition.yaml`. ## Heading Depth Rule (MANDATORY) @@ -110,15 +105,10 @@ available - keep internal structure flat to avoid excessive nesting. Write technical documentation for clarity and compliance verification: - **Clear and Concise**: Use direct language and avoid unnecessary complexity. - Regulatory reviewers must understand content quickly. -- **Structured Sections**: Use consistent heading hierarchy and section - organization. Enables automated processing and review. -- **Specific Examples**: Include concrete examples with actual values rather - than placeholders. Supports implementation verification. +- **Structured Sections**: Use consistent heading hierarchy and section organization. +- **Specific Examples**: Include concrete examples with actual values rather than placeholders. - **Current Information**: Keep documentation synchronized with code changes. - Outdated documentation invalidates compliance evidence. -- **Traceable Content**: Link documentation to requirements and implementation - where applicable for audit trails. +- **Traceable Content**: Link documentation to requirements and implementation where applicable. ## References Sections @@ -138,26 +128,16 @@ Instead use **verbal references** - plain prose that identifies the target by na > > Refer to the *System Requirements* document for the full specification. -Verbal references are readable by both AI agents and humans in any rendering environment. - # Markdown Format Requirements -Markdown documentation in this repository must follow the formatting standards -defined in `.markdownlint-cli2.yaml` (subject to any exclusions configured there) -for consistency and professional presentation: - -- **120 Character Line Limit**: Keep lines 120 characters or fewer for readability. - Break long lines naturally at punctuation or logical breaks. -- **No Trailing Whitespace**: Remove all trailing spaces and tabs from line - endings to prevent formatting inconsistencies. -- **Blank Lines Around Headings**: Include a blank line both before and after - each heading to improve document structure and readability. -- **Blank Lines Around Lists**: Include a blank line both before and after - numbered and bullet lists to ensure proper rendering and visual separation. -- **ATX-Style Headers**: Use `#` syntax for headers instead of underline style - for consistency across all documentation. -- **Consistent List Indentation**: Use 2-space indentation for nested list - items to maintain uniform formatting. +Follow `.markdownlint-cli2.yaml` formatting standards: + +- **120 Character Line Limit**: Keep lines 120 characters or fewer; break at punctuation or logical breaks. +- **No Trailing Whitespace**: Remove all trailing spaces and tabs. +- **Blank Lines Around Headings**: Include a blank line before and after each heading. +- **Blank Lines Around Lists**: Include a blank line before and after numbered and bullet lists. +- **ATX-Style Headers**: Use `#` syntax, not underline style. +- **Consistent List Indentation**: Use 2-space indentation for nested list items. # Auto-Generated Content (CRITICAL) @@ -168,8 +148,6 @@ build outputs that are overwritten on every CI run: respective `docs/` sections, or in `docs/generated/` for final release artifacts - **Source Modification**: Update source files (requirements YAML, `.reviewmark.yaml`, tool configuration) instead of generated output -- **Tool Integration**: Generated content integrates with CI/CD pipelines and - manual changes disrupt automation # README.md Best Practices @@ -192,20 +170,12 @@ Structure README.md for both human readers and AI agent processing: - **Code Block Languages**: Specify language for syntax highlighting and tool processing - **Clear Prerequisites**: List exact version requirements and dependencies -## Quality Guidelines - -- **Scannable Structure**: Use bullet points, headings, and short paragraphs -- **Current Examples**: Verify all code examples work with current version -- **Link Validation**: Ensure all external links are accessible and current -- **Consistent Tone**: Professional, helpful tone appropriate for technical audience - # Quality Checks Before submitting technical documentation, verify: - [ ] Documentation organized under `docs/` following standard folder structure - [ ] Pandoc collections include `introduction.md` with Purpose and Scope sections -- [ ] Content follows clear and concise writing guidelines with specific examples - [ ] No modifications made to auto-generated markdown files in compliance folders - [ ] README.md includes all required sections with absolute URLs and concrete examples - [ ] Documentation integrated into ReviewMark review-sets for formal review diff --git a/.github/standards/testing-principles.md b/.github/standards/testing-principles.md index 73974ff..917463e 100644 --- a/.github/standards/testing-principles.md +++ b/.github/standards/testing-principles.md @@ -3,11 +3,6 @@ name: Testing Principles description: Follow these standards when developing any software tests. --- -# Testing Principles Standards - -This document defines universal testing principles and quality standards for test development within -Continuous Compliance environments. - # Test Dependency Boundaries (MANDATORY) Respect software item hierarchy boundaries to ensure review-sets can validate proper architectural scope. diff --git a/.github/standards/verification-documentation.md b/.github/standards/verification-documentation.md index 8dc4408..494e40f 100644 --- a/.github/standards/verification-documentation.md +++ b/.github/standards/verification-documentation.md @@ -28,7 +28,8 @@ docs/verification/ └── {package-name}.md # heading depth ## ``` -Subsystems may nest recursively. Each file's heading depth equals its folder depth under `docs/verification/`. +All sections in every file are mandatory; write "N/A - {justification}" rather than removing any. +Determine subsystem vs. unit classification from `docs/design/introduction.md` — folder depth does not determine classification. # introduction.md (MANDATORY) @@ -41,50 +42,44 @@ Must include: # System Verification Design (MANDATORY) -Create `{system-name}.md` (`#` heading) and `{system-name}/` folder. All sections mandatory; -write "N/A - {justification}" rather than removing any section: +Create `{system-name}.md` (`#` heading) and `{system-name}/` folder: -- **Verification Strategy**: test types (unit, integration, end-to-end), framework, project structure +- **Verification Approach**: test types (unit, integration, end-to-end), framework, project structure - **Test Environment**: OS, runtime, external services, files, or configuration required - **Acceptance Criteria**: what constitutes a passing system test (IEC 62304 §5.7.2) -- **System-Level Test Scenarios**: named scenarios for each system requirement -- **Requirements Coverage**: requirement → scenario(s) → test method(s) mapping +- **Test Scenarios**: named scenarios for each system requirement # Subsystem Verification Design (MANDATORY) -Place `{subsystem-name}.md` in the **parent** folder; create `{subsystem-name}/` for children. -All sections mandatory; write "N/A - {justification}" rather than removing any section: +Place `{subsystem-name}.md` in the **parent** folder; create `{subsystem-name}/` for children: -- **Verification Strategy**: integration test approach and mocking at subsystem boundary +- **Verification Approach**: integration test approach and mocking at subsystem boundary - **Test Environment**: any environment setup beyond the standard test runner - **Acceptance Criteria**: what constitutes a passing subsystem test (IEC 62304 §5.5.2) - **Test Scenarios**: named scenarios including boundary conditions, error paths, and normal operation -- **Requirements Coverage**: requirement → scenario(s) → test method(s) mapping # Unit Verification Design (MANDATORY) -Place `{unit-name}.md` in the **parent** folder. All sections mandatory; -write "N/A - {justification}" rather than removing any section: +Place `{unit-name}.md` in the **parent** folder: - **Verification Approach**: what is mocked/stubbed and why; injected vs. real dependencies - **Test Environment**: any environment setup beyond the standard test runner - **Acceptance Criteria**: what constitutes passing unit tests (IEC 62304 §5.5.2) - **Test Scenarios**: named scenarios including boundary values, error paths, and normal operation -- **Requirements Coverage**: requirement → scenario(s) → test method(s) mapping # OTS Verification Evidence (when OTS items exist) Create `docs/verification/ots.md` (`#` heading) covering the overall OTS verification strategy. For each OTS item, create `docs/verification/ots/{ots-name}.md` (`##` heading) covering: -verification approach (self-validation, integration tests, vendor evidence) and requirements coverage. +verification approach (self-validation, integration tests, vendor evidence). # Shared Package Verification Evidence (when Shared Packages exist) Create `docs/verification/shared.md` (`#` heading) covering the overall Shared Package verification strategy. For each Shared Package, create `docs/verification/shared/{package-name}.md` (`##` heading) covering: -verification approach and requirements coverage. +verification approach. # Writing Guidelines @@ -97,14 +92,10 @@ verification approach and requirements coverage. - [ ] `introduction.md` includes Companion Artifact Structure - [ ] Each file's heading depth matches its folder depth - [ ] All folders use kebab-case mirroring source structure -- [ ] System verification includes all mandatory sections (Verification Strategy, Test Environment, - Acceptance Criteria, System-Level Test Scenarios, Requirements Coverage) -- [ ] Subsystem verification includes all mandatory sections (Verification Strategy, Test Environment, - Acceptance Criteria, Test Scenarios, Requirements Coverage) -- [ ] Unit verification includes all mandatory sections (Verification Approach, Test Environment, - Acceptance Criteria, Test Scenarios, Requirements Coverage) +- [ ] Each system/subsystem/unit file includes all mandatory sections (Verification Approach, + Test Environment, Acceptance Criteria, Test Scenarios) - [ ] Non-applicable mandatory sections contain "N/A - {justification}" -- [ ] Every requirement is mapped to at least one named test scenario +- [ ] Requirements-to-test coverage is tracked via the ReqStream trace matrix, not in these documents - [ ] `docs/verification/ots.md` and `docs/verification/ots/{ots-name}.md` exist when OTS items are present - [ ] `docs/verification/shared.md` and `docs/verification/shared/{package-name}.md` exist when Shared Packages are present - [ ] Documents are integrated into ReviewMark review-sets diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2b8f10b..39018ec 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -463,7 +463,7 @@ jobs: dotnet versionmark --capture --job-id "build-docs" \ --output "artifacts/versionmark-build-docs.json" -- \ dotnet git node npm pandoc weasyprint sarifmark sonarmark reqstream \ - buildmark versionmark reviewmark fileassert + buildmark versionmark reviewmark fileassert sysml2tools echo "✓ Tool versions captured" # === PREPARE DOCUMENT OUTPUT === @@ -722,6 +722,20 @@ jobs: shell: bash run: mkdir -p docs/design/generated + - name: Run SysML2Tools self-validation + run: > + dotnet sysml2tools + --validate + --results artifacts/sysml2tools-self-validation.trx + + - name: Render Software Structure diagrams with SysML2Tools + shell: pwsh + run: > + dotnet sysml2tools render + --output docs/design/generated --format svg + 'docs/sysml2/model/**/*.sysml' + 'docs/sysml2/views/design-views.sysml' + - name: Generate Design HTML with Pandoc shell: bash run: > diff --git a/.reviewmark.yaml b/.reviewmark.yaml index 8da902a..d7f1faa 100644 --- a/.reviewmark.yaml +++ b/.reviewmark.yaml @@ -10,6 +10,7 @@ needs-review: - "requirements.yaml" # Root requirements file - "**/*.cs" # All C# source and test files - "docs/reqstream/**/*.yaml" # Requirements files + - "docs/sysml2/**/*.sysml" # SysML2 architecture model - "docs/design/**/*.md" # Design documents - "docs/verification/**/*.md" # Verification documents - "docs/user_guide/**/*.md" # User guide documents @@ -26,22 +27,37 @@ evidence-source: type: url location: https://raw.githubusercontent.com/demaconsulting/NuGetCacheTool/reviews/index.json +# Global context: shown to every reviewer for orientation, not fingerprinted. +context: + - docs/design/introduction.md + # Review sets following hierarchical scope principles. # Each review-set focuses on a single compliance question with manageable file counts. reviews: # Purpose - id: Purpose - title: Review of user-facing capabilities and system promises + title: Review that README and User Guide are Coherent and Complete paths: - "README.md" - "docs/user_guide/**/*.md" - - "docs/reqstream/nuget-cache-tool.yaml" + + # Decomposition + - id: Decomposition + title: Review that NuGetCacheTool Decomposition Addresses the Stated Purpose + context: + - "README.md" + - "docs/user_guide/**/*.md" + paths: + - "requirements.yaml" - "docs/design/introduction.md" - - "docs/design/nuget-cache-tool.md" + - "docs/sysml2/**/*.sysml" - # NuGetCacheTool - Specials + # NuGetCacheTool - System-level - id: NuGetCacheTool-Architecture - title: Review of NuGet Cache Tool system architecture and operational validation + title: Review that NuGetCacheTool Architecture Satisfies Requirements + context: + - "README.md" + - "docs/user_guide/**/*.md" paths: - "docs/reqstream/nuget-cache-tool.yaml" - "docs/design/introduction.md" @@ -52,26 +68,41 @@ reviews: - "test/**/IntegrationTests.cs" - id: NuGetCacheTool-Design - title: Review of NuGet Cache Tool architectural and design consistency - paths: + title: Review that NuGetCacheTool Design is Consistent and Complete + context: - "docs/reqstream/nuget-cache-tool.yaml" + paths: - "docs/reqstream/nuget-cache-tool/platform-requirements.yaml" - "docs/design/introduction.md" - "docs/design/nuget-cache-tool.md" - "docs/design/nuget-cache-tool/**/*.md" + - "docs/design/ots.md" + + - id: NuGetCacheTool-Verification + title: Review that NuGetCacheTool Verification is Consistent and Complete + context: + - "docs/reqstream/nuget-cache-tool.yaml" + paths: - "docs/verification/introduction.md" + - "docs/verification/nuget-cache-tool.md" + - "docs/verification/nuget-cache-tool/**/*.md" + - "docs/verification/ots.md" - id: NuGetCacheTool-AllRequirements - title: Review of NuGet Cache Tool requirements quality and traceability + title: Review that All NuGetCacheTool Requirements are Complete + context: + - "docs/design/nuget-cache-tool.md" + - "docs/reqstream/nuget-cache-tool.yaml" paths: - "requirements.yaml" - - "docs/reqstream/nuget-cache-tool.yaml" - "docs/reqstream/nuget-cache-tool/**/*.yaml" - - "docs/reqstream/ots/*.yaml" - # NuGetCacheTool - Program + # NuGetCacheTool - Program (top-level unit) - id: NuGetCacheTool-Program - title: Review of NuGet Cache Tool Program unit implementation + title: Review that NuGetCacheTool Program Implementation is Correct + context: + - "docs/design/nuget-cache-tool.md" + - "docs/reqstream/nuget-cache-tool.yaml" paths: - "docs/reqstream/nuget-cache-tool/program.yaml" - "docs/design/nuget-cache-tool/program.md" @@ -81,7 +112,10 @@ reviews: # NuGetCacheTool - Cli - id: NuGetCacheTool-Cli - title: Review of NuGet Cache Tool Cli subsystem architecture and interfaces + title: Review that NuGetCacheTool Cli Satisfies Subsystem Requirements + context: + - "docs/design/nuget-cache-tool.md" + - "docs/reqstream/nuget-cache-tool.yaml" paths: - "docs/reqstream/nuget-cache-tool/cli.yaml" - "docs/design/nuget-cache-tool/cli.md" @@ -89,7 +123,12 @@ reviews: - "test/**/Cli/CliTests.cs" - id: NuGetCacheTool-Cli-Context - title: Review of NuGet Cache Tool Context unit implementation + title: Review that NuGetCacheTool Cli Context Implementation is Correct + context: + - "docs/design/nuget-cache-tool.md" + - "docs/reqstream/nuget-cache-tool.yaml" + - "docs/design/nuget-cache-tool/cli.md" + - "docs/reqstream/nuget-cache-tool/cli.yaml" paths: - "docs/reqstream/nuget-cache-tool/cli/context.yaml" - "docs/design/nuget-cache-tool/cli/context.md" @@ -99,7 +138,10 @@ reviews: # NuGetCacheTool - SelfTest - id: NuGetCacheTool-SelfTest - title: Review of NuGet Cache Tool SelfTest subsystem architecture and interfaces + title: Review that NuGetCacheTool SelfTest Satisfies Subsystem Requirements + context: + - "docs/design/nuget-cache-tool.md" + - "docs/reqstream/nuget-cache-tool.yaml" paths: - "docs/reqstream/nuget-cache-tool/self-test.yaml" - "docs/design/nuget-cache-tool/self-test.md" @@ -107,7 +149,12 @@ reviews: - "test/**/SelfTest/SelfTestTests.cs" - id: NuGetCacheTool-SelfTest-Validation - title: Review of NuGet Cache Tool Validation unit implementation + title: Review that NuGetCacheTool SelfTest Validation Implementation is Correct + context: + - "docs/design/nuget-cache-tool.md" + - "docs/reqstream/nuget-cache-tool.yaml" + - "docs/design/nuget-cache-tool/self-test.md" + - "docs/reqstream/nuget-cache-tool/self-test.yaml" paths: - "docs/reqstream/nuget-cache-tool/self-test/validation.yaml" - "docs/design/nuget-cache-tool/self-test/validation.md" @@ -117,7 +164,10 @@ reviews: # NuGetCacheTool - Utilities - id: NuGetCacheTool-Utilities - title: Review of NuGet Cache Tool Utilities subsystem architecture and interfaces + title: Review that NuGetCacheTool Utilities Satisfies Subsystem Requirements + context: + - "docs/design/nuget-cache-tool.md" + - "docs/reqstream/nuget-cache-tool.yaml" paths: - "docs/reqstream/nuget-cache-tool/utilities.yaml" - "docs/design/nuget-cache-tool/utilities.md" @@ -125,7 +175,12 @@ reviews: - "test/**/Utilities/UtilitiesTests.cs" - id: NuGetCacheTool-Utilities-PathHelpers - title: Review of NuGet Cache Tool PathHelpers unit implementation + title: Review that NuGetCacheTool Utilities PathHelpers Implementation is Correct + context: + - "docs/design/nuget-cache-tool.md" + - "docs/reqstream/nuget-cache-tool.yaml" + - "docs/design/nuget-cache-tool/utilities.md" + - "docs/reqstream/nuget-cache-tool/utilities.yaml" paths: - "docs/reqstream/nuget-cache-tool/utilities/path-helpers.yaml" - "docs/design/nuget-cache-tool/utilities/path-helpers.md" @@ -134,7 +189,12 @@ reviews: - "test/**/Utilities/PathHelpersTests.cs" - id: NuGetCacheTool-Utilities-TemporaryDirectory - title: Review of NuGet Cache Tool TemporaryDirectory unit implementation + title: Review that NuGetCacheTool Utilities TemporaryDirectory Implementation is Correct + context: + - "docs/design/nuget-cache-tool.md" + - "docs/reqstream/nuget-cache-tool.yaml" + - "docs/design/nuget-cache-tool/utilities.md" + - "docs/reqstream/nuget-cache-tool/utilities.yaml" paths: - "docs/reqstream/nuget-cache-tool/utilities/temporary-directory.yaml" - "docs/design/nuget-cache-tool/utilities/temporary-directory.md" @@ -144,61 +204,82 @@ reviews: # OTS Items - id: OTS-BuildMark - title: Review of BuildMark OTS verification evidence + title: Review that BuildMark Provides Required Functionality paths: - "docs/reqstream/ots/buildmark.yaml" - "docs/verification/ots/buildmark.md" - id: OTS-FileAssert - title: Review of FileAssert OTS verification evidence + title: Review that FileAssert Provides Required Functionality paths: - "docs/reqstream/ots/fileassert.yaml" - "docs/verification/ots/fileassert.md" + - id: OTS-NuGetCaching + title: Review that DemaConsulting.NuGet.Caching Provides Required Functionality + paths: + - "docs/reqstream/ots/nuget-caching.yaml" + - "docs/design/ots/nuget-caching.md" + - "docs/verification/ots/nuget-caching.md" + - id: OTS-Pandoc - title: Review of Pandoc OTS verification evidence + title: Review that Pandoc Provides Required Functionality paths: - "docs/reqstream/ots/pandoc.yaml" - "docs/verification/ots/pandoc.md" - id: OTS-ReqStream - title: Review of ReqStream OTS verification evidence + title: Review that ReqStream Provides Required Functionality paths: - "docs/reqstream/ots/reqstream.yaml" - "docs/verification/ots/reqstream.md" - id: OTS-ReviewMark - title: Review of ReviewMark OTS verification evidence + title: Review that ReviewMark Provides Required Functionality paths: - "docs/reqstream/ots/reviewmark.yaml" - "docs/verification/ots/reviewmark.md" - id: OTS-SarifMark - title: Review of SarifMark OTS verification evidence + title: Review that SarifMark Provides Required Functionality paths: - "docs/reqstream/ots/sarifmark.yaml" - "docs/verification/ots/sarifmark.md" - id: OTS-SonarMark - title: Review of SonarMark OTS verification evidence + title: Review that SonarMark Provides Required Functionality paths: - "docs/reqstream/ots/sonarmark.yaml" - "docs/verification/ots/sonarmark.md" + - id: OTS-SysML2Tools + title: Review that SysML2Tools Provides Required Functionality + paths: + - "docs/reqstream/ots/sysml2tools.yaml" + - "docs/design/ots/sysml2tools.md" + - "docs/verification/ots/sysml2tools.md" + + - id: OTS-TestResults + title: Review that DemaConsulting.TestResults Provides Required Functionality + paths: + - "docs/reqstream/ots/test-results.yaml" + - "docs/design/ots/test-results.md" + - "docs/verification/ots/test-results.md" + - id: OTS-VersionMark - title: Review of VersionMark OTS verification evidence + title: Review that VersionMark Provides Required Functionality paths: - "docs/reqstream/ots/versionmark.yaml" - "docs/verification/ots/versionmark.md" - id: OTS-WeasyPrint - title: Review of WeasyPrint OTS verification evidence + title: Review that WeasyPrint Provides Required Functionality paths: - "docs/reqstream/ots/weasyprint.yaml" - "docs/verification/ots/weasyprint.md" - id: OTS-xUnit - title: Review of xUnit OTS verification evidence + title: Review that xUnit Provides Required Functionality paths: - "docs/reqstream/ots/xunit.yaml" - "docs/verification/ots/xunit.md" diff --git a/.versionmark.yaml b/.versionmark.yaml index 9275778..0074ac8 100644 --- a/.versionmark.yaml +++ b/.versionmark.yaml @@ -72,3 +72,8 @@ tools: fileassert: command: dotnet tool list regex: '(?i)demaconsulting\.fileassert\s+(?\d+\.\d+\.\d+(?:-[a-zA-Z0-9.]+)?)' + + # sysml2tools (DemaConsulting.Sysml2Tools.Tool from dotnet tool list) + sysml2tools: + command: dotnet tool list + regex: '(?i)demaconsulting\.sysml2tools\.tool\s+(?\d+\.\d+\.\d+(?:-[a-zA-Z0-9.]+)?)' diff --git a/AGENTS.md b/AGENTS.md index 2644883..a58024d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,7 @@ # Project Overview -- **name**: NuGet Cache Tool +- **project-name**: NuGet Cache Tool +- **organization**: DEMA Consulting - **description**: A .NET global tool that ensures NuGet packages are cached in the global packages folder - **languages**: C# - **technologies**: .NET 8/9/10, NuGet, xUnit @@ -17,6 +18,7 @@ │ ├── requirements_doc/ │ ├── requirements_report/ │ ├── reqstream/ +│ ├── sysml2/ │ ├── user_guide/ │ └── verification/ ├── src/ @@ -25,20 +27,27 @@ └── DemaConsulting.NuGet.CacheTool.Tests/ ``` +# Language and Spelling (ALL Agents) + +Always use **US English** spelling in all output (code, comments, documentation, +commit messages, and reports). + # Reference Template This repository follows a reference template for structure and file conventions. -- **Template URL**: `https://github.com/demaconsulting/Agents/raw/refs/heads/template` +- **template-url**: `https://github.com/demaconsulting/Agents/raw/refs/heads/template` - **Repository map**: `{template-url}/repository-map.md` - **Template files**: `{template-url}/{file-path}` for files described in the map # Codebase Navigation (ALL Agents) -When working with source code, design, or requirements artifacts, read -`docs/design/introduction.md` first. It provides the software structure, -folder layout, and companion artifact locations. Use it as the primary map -before searching the filesystem. +When working with source code, design, or requirements artifacts, query the SysML2 +architecture model under `docs/sysml2/` first (see the `sysml2tools-query` skill) to +understand software structure, purpose, and relationships. Fall back to +`docs/design/introduction.md` for the human-facing narrative, folder layout, and +companion artifact locations, and use it as the primary map when the model doesn't +yet cover something. # Key Configuration Files @@ -53,6 +62,7 @@ before searching the filesystem. - **`package.json`** - Node.js dependencies for formatting tools - **`requirements.yaml`** - Root requirements file with includes - **`pip-requirements.txt`** - Python dependencies for yamllint and yamlfix +- **`docs/sysml2/`** - SysML2 architecture model; authoritative source for software structure - **`fix.ps1`** - Applies all auto-fixers silently (dotnet format, markdown, YAML). Always exits 0. - **`build.ps1`** - Builds the solution and runs all tests. @@ -69,8 +79,8 @@ from `.github/standards/`. Use this matrix to determine which to load: - **Design docs**: `software-items.md`, `design-documentation.md`, `technical-documentation.md` - **Verification docs**: `software-items.md`, `verification-documentation.md`, `technical-documentation.md` - **Review configuration**: `software-items.md`, `reviewmark-usage.md` +- **Software structure**: `sysml2-modeling.md` - **Any documentation**: `technical-documentation.md` -- **Structural audit**: `template-sync` agent Load only the standards relevant to your specific task scope. @@ -80,11 +90,15 @@ The default agent should handle simple, straightforward tasks directly. Delegate to specialized agents only for specific scenarios: - **Pre-PR lint cleanup** (fix all lint issues before pull request) → Call the lint-fix agent -- **Light development work** (small fixes, simple features) → Call the developer agent +- **Scoped fixes with no new user-visible behavior** (PR review comments, doc + corrections, known bug fixes with defined root cause) → Call the developer agent - **Light quality checking** (basic validation) → Call the quality agent -- **Formal feature implementation** (complex, multi-step) → Call the implementation agent -- **Formal bug resolution** (complex debugging, systematic fixes) → Call the implementation agent +- **Any change introducing new user-visible behavior** (features, enhancements, + new commands or options) → Call the implementation agent +- **Formal bug resolution** (complex debugging, unknown root cause) → Call the implementation agent - **Formal reviews** (compliance verification, detailed analysis) → Call the formal-review agent +- **Structural audit**: (repository layout vs. template) → Call the template-sync agent +- **Implementation planning only** (review a plan before committing to implementation) → Call the planning agent # Agent Reporting (Specialized Agents Must Follow) @@ -93,16 +107,16 @@ Specialized agents MUST generate a completion report: 1. Save to `.agent-logs/{agent-name}-{subject}-{unique-id}.md` where `{subject}` is a kebab-case task summary (max 5 words) and `{unique-id}` is a short unique suffix (e.g., 8-char hex or timestamp) -2. Start with `**Result**: (SUCCEEDED|FAILED)` as the first metadata field +2. Start with `**Result**: (SUCCEEDED|FAILED|INCOMPLETE)` as the first metadata field 3. Include the agent-specific report sections defined in each agent's prompt 4. Return the summary to the caller Result semantics for orchestrator decision-making: -- **SUCCEEDED**: Work completed and all applicable quality gates met +- **SUCCEEDED**: Work completed and all quality gates applicable to that agent's scope met - **FAILED**: Work could not be completed or quality gates not met - **INCOMPLETE**: Work cannot proceed without information only the user can - provide (implementation agent only) + provide (implementation, planning, and template-sync agents) # Formatting (After Making Changes) @@ -120,7 +134,7 @@ responsibility - invoke the lint-fix agent once before submitting a pull request ## CI Quality Tools CI runs `lint.ps1` which checks: markdownlint-cli2, cspell, yamllint, dotnet format, -reqstream, versionmark, and reviewmark. +reqstream, versionmark, reviewmark, and sysml2tools. # Scope Discipline (ALL Agents Must Follow) diff --git a/docs/design/definition.yaml b/docs/design/definition.yaml index 3fb0508..2d99707 100644 --- a/docs/design/definition.yaml +++ b/docs/design/definition.yaml @@ -6,6 +6,7 @@ resource-path: - docs/design/nuget-cache-tool/self-test - docs/design/nuget-cache-tool/utilities - docs/design/ots + - docs/design/generated - docs/template input-files: @@ -23,6 +24,7 @@ input-files: - docs/design/ots.md - docs/design/ots/nuget-caching.md - docs/design/ots/test-results.md + - docs/design/ots/sysml2tools.md template: template.html table-of-contents: true number-sections: true diff --git a/docs/design/introduction.md b/docs/design/introduction.md index 84001ba..2965a14 100644 --- a/docs/design/introduction.md +++ b/docs/design/introduction.md @@ -9,43 +9,47 @@ the software structure and folder layout. ## Scope -This design documentation covers all software items of the NuGet Cache Tool system. +This design documentation covers the following software items: + +Local items: + +- **NuGetCacheTool**: system, subsystem, and unit design for all local components. + +OTS items: + +- **DemaConsulting.NuGet.Caching**: integration and usage design. +- **DemaConsulting.TestResults**: integration and usage design. +- **SysML2Tools**: integration and usage design (the one build-time-only OTS tool with a + dedicated design document, since it generates the SysML2 model and diagrams referenced + below). + It applies to the current release and all subsequent releases until superseded. -The following items are explicitly excluded from this design documentation: -`DemaConsulting.NuGet.CacheTool.Tests` and CI/CD pipeline configuration. +The following topics are out of scope: + +- External library internals +- Build pipeline configuration +- Test projects (`DemaConsulting.NuGet.CacheTool.Tests`) +- All other build-pipeline-only OTS tooling (BuildMark, FileAssert, Pandoc, ReqStream, + ReviewMark, SarifMark, SonarMark, VersionMark, WeasyPrint, and xUnit); see *OTS Verification* + in the verification documentation for their requirements and verification coverage ## Software Structure -```text -NuGetCacheTool (System) -├── CLI (Subsystem) -│ └── Context (Unit) -├── SelfTest (Subsystem) -│ └── Validation (Unit) -├── Utilities (Subsystem) -│ ├── TemporaryDirectory (Unit) -│ └── PathHelpers (Unit) -└── Program (Unit) - -OTS Items -├── DemaConsulting.NuGet.Caching -└── DemaConsulting.TestResults -``` +The software structure is modeled in SysML2 under `docs/sysml2/` and rendered to the +diagram below by SysML2Tools as part of the build pipeline. AI agents should query the +SysML2 model directly (see the `sysml2tools-query` skill) rather than parsing this +diagram or the prose below. + +![Software Structure](SoftwareStructureView.svg) ## Folder Layout -```text -src/DemaConsulting.NuGet.CacheTool/ -├── Cli/ -│ └── Context.cs — CLI subsystem: argument parsing and output management -├── SelfTest/ -│ └── Validation.cs — SelfTest subsystem: self-validation test execution -├── Utilities/ -│ ├── PathHelpers.cs — Utilities subsystem: safe path combination utilities -│ └── TemporaryDirectory.cs — Utilities subsystem: disposable temporary directory -└── Program.cs — top-level entry point and application orchestration -``` +- **src/** - source files and projects + - **DemaConsulting.NuGet.CacheTool/** - NuGetCacheTool system source + - **Cli/** - CLI subsystem + - **SelfTest/** - SelfTest subsystem + - **Utilities/** - Utilities subsystem ## Companion Artifact Structure @@ -59,9 +63,12 @@ Each local software item has corresponding artifacts in parallel directory trees OTS items have integration/usage design documentation parallel to system folders: -- Requirements: `docs/reqstream/ots/nuget-caching.yaml`, `docs/reqstream/ots/test-results.yaml` -- Design: `docs/design/ots/nuget-caching.md`, `docs/design/ots/test-results.md` -- Verification: `docs/verification/ots/nuget-caching.md`, `docs/verification/ots/test-results.md` +- Requirements: `docs/reqstream/ots/nuget-caching.yaml`, `docs/reqstream/ots/test-results.yaml`, + `docs/reqstream/ots/sysml2tools.yaml` +- Design: `docs/design/ots/nuget-caching.md`, `docs/design/ots/test-results.md`, + `docs/design/ots/sysml2tools.md` +- Verification: `docs/verification/ots/nuget-caching.md`, `docs/verification/ots/test-results.md`, + `docs/verification/ots/sysml2tools.md` Review-sets: defined in `.reviewmark.yaml` diff --git a/docs/design/nuget-cache-tool.md b/docs/design/nuget-cache-tool.md index 9e78360..e1c071d 100644 --- a/docs/design/nuget-cache-tool.md +++ b/docs/design/nuget-cache-tool.md @@ -1,8 +1,10 @@ # NuGet Cache Tool System Design +![NuGetCacheTool Structure](NuGetCacheToolView.svg) + ## Architecture -The NuGet Cache Tool is a .NET global tool organized into two subsystems and one +The NuGet Cache Tool is a .NET global tool organized into three subsystems and one top-level unit, all residing in a single assembly. ### Major Components @@ -24,7 +26,7 @@ top-level unit, all residing in a single assembly. | --------- | ----------- | | CLI entry point (`nuget-cache [options] [package:version ...]`) | Main command-line invocation pattern accepted by the tool | | stdout | Package paths are written one per line on success; banner and help text on non-version invocations | -| stderr (`Error: {message}`) | Error messages are written to stderr when an error occurs and silent mode is not active | +| stderr | Error messages are written to stderr when an error occurs and silent mode is not active; `Program.Main` prefixes caught top-level exceptions with `"Error: "`, while `Context.WriteError` writes caller-supplied text unchanged | | Exit code | 0 = success; non-zero = failure | ## Dependencies @@ -67,7 +69,7 @@ args ### Platform Constraints - The tool targets .NET 8, .NET 9, and .NET 10 on Windows, Linux, and macOS (see - [Platform and Runtime Targeting](#platform-and-runtime-targeting) below) + *Platform and Runtime Targeting* below) - All platform-specific behavior is delegated to the .NET SDK and the `DemaConsulting.NuGet.Caching` library; the tool itself contains no platform-conditional code @@ -120,7 +122,7 @@ These tests use the same `Program.Run` path as normal usage, capturing output vi | --------- | ------- | --------- | | `NuGetCache_VersionDisplay` | `--version` | Version string is present and contains dots | | `NuGetCache_HelpDisplay` | `--help` | Output contains `Usage:` and `Options:` | -| `NuGetCache_CachePackage` | `DemaConsulting.NuGet.Caching:0.1.0` | A non-empty package path is returned | +| `NuGetCache_CachePackage` | `DemaConsulting.NuGet.Caching:{fixed test version}` | A non-empty package path is returned, using a fixed test version pinned independently of the tool's own dependency version | When `--results` is supplied, these tests are emitted as test results (TRX (`.trx`) or JUnit XML (`.xml`), depending on the `--results` file extension) and serve as diff --git a/docs/design/nuget-cache-tool/cli.md b/docs/design/nuget-cache-tool/cli.md index a8a4a8b..752bda4 100644 --- a/docs/design/nuget-cache-tool/cli.md +++ b/docs/design/nuget-cache-tool/cli.md @@ -1,5 +1,7 @@ ## CLI Subsystem Design +![Cli Structure](CliView.svg) + ### Overview The CLI subsystem provides command-line interface functionality for the NuGet Cache Tool. @@ -33,7 +35,6 @@ The `Context` unit exposes the following public API: | `Silent` | `bool` property | True when `--silent` was supplied | | `Validate` | `bool` property | True when `--validate` was supplied | | `Packages` | `IReadOnlyList` property | List of `package:version` strings to cache | -| `LogFile` | `string?` property | Path to log file supplied with `--log`, or null | | `ResultsFile` | `string?` property | Path to results file supplied with `--results`, or null | | `ExitCode` | `int` property | Current exit code; 0 = success, 1 = failure | | `WriteLine(string)` | method | Writes a line to stdout (and log file if open); suppressed in silent mode | @@ -47,7 +48,7 @@ The `Context` unit exposes the following public API: | Dependency | Direction | Description | | ---------- | --------- | ----------- | | `Program` | Top-level unit (entry point) | Creates `Context` and dispatches to CLI subsystem | -| `SelfTest` subsystem | Downstream | CLI passes `Context` to `Validation.Run` | +| `SelfTest` subsystem | Downstream | `Program` passes the `Context` it created (via CLI) to `Validation.Run`; `Validation` consumes `Context` for output and results-path resolution | #### Error Handling @@ -57,7 +58,7 @@ The `Context` unit exposes the following public API: | | identifying the unsupported argument | | `--log` flag without a value | `Create()` throws `ArgumentException` | | `--results` flag without a value | `Create()` throws `ArgumentException` | -| Log file cannot be opened | `Create()` throws `ArgumentException` with the underlying I/O error message | +| Log file cannot be opened | `Create()` throws `InvalidOperationException` with the underlying I/O error message | | `WriteError()` called | Sets `ExitCode` to 1; writes message to stderr (and log file if open) | Exit code semantics: `ExitCode` starts at 0 and is set to 1 on the first call to `WriteError()`. diff --git a/docs/design/nuget-cache-tool/cli/context.md b/docs/design/nuget-cache-tool/cli/context.md index 585d326..036b811 100644 --- a/docs/design/nuget-cache-tool/cli/context.md +++ b/docs/design/nuget-cache-tool/cli/context.md @@ -1,5 +1,7 @@ ### Context Unit Design +![Cli Structure](CliView.svg) + #### Purpose The `Context` class provides command-line argument parsing and centralized output @@ -80,26 +82,34 @@ value follows. `WriteError(string)` writes to stderr (unless silent), writes to the log file, and sets `_hasErrors = true`, which causes `ExitCode` to return `1`. -#### Interactions - -- **Consumed by `Program`**: `Program.Main` creates the context and passes it to `Program.Run` -- **Consumed by `Validation`**: `Validation.Run` uses `Context` for output and results path -- **Consumed by `PathHelpers`**: indirectly via `Validation.Run` calling `SafePathCombine` - #### Error Handling | Scenario | Behavior | | -------- | -------- | | Unknown argument in `Create()` | Throws `ArgumentException` identifying the unsupported argument | | `--log` or `--results` flag without a value | Throws `ArgumentException` | -| Log file cannot be opened | Throws `ArgumentException` wrapping the underlying I/O exception message | +| Log file cannot be opened | Throws `InvalidOperationException` wrapping the underlying I/O exception message | | `WriteError()` called | Sets `_hasErrors = true` (causing `ExitCode` to return 1); writes message to stderr and log file | | `WriteLine()` called in silent mode | Suppresses console output; still writes to log file if open | | `Dispose()` called multiple times | Safe; the `StreamWriter` is set to null after first disposal | -Argument-parsing errors propagate to `Program.Main`, which catches `ArgumentException` and +Argument-parsing errors propagate to `Program.Main`, which catches both `ArgumentException` and +`InvalidOperationException` (for example, when the log file cannot be opened) and writes the message to `Console.Error` before returning exit code 1. +#### Dependencies + +`Context` depends only on the .NET BCL: `System.IO.StreamWriter` for log-file output and +`System.Console` for stdout/stderr. It does not depend on any other unit in the NuGet +Cache Tool system; it is a low-level building block consumed by `Program` and `Validation`. + +#### Callers + +- **`Program`**: `Program.Main` creates the context via `Context.Create` and passes it to + `Program.Run`. +- **`Validation`** (SelfTest subsystem): `Validation.Run` receives a `Context` instance and + uses it for output and results-path resolution. + #### Resource Management `Context` implements `IDisposable`. The `Dispose()` method flushes and closes the diff --git a/docs/design/nuget-cache-tool/program.md b/docs/design/nuget-cache-tool/program.md index be4d865..1243ecc 100644 --- a/docs/design/nuget-cache-tool/program.md +++ b/docs/design/nuget-cache-tool/program.md @@ -1,5 +1,7 @@ ## Program Unit Design +![NuGetCacheTool Structure](NuGetCacheToolView.svg) + ### Purpose `Program` is the main entry point and orchestration unit. It creates the `Context`, @@ -81,7 +83,7 @@ A third handler catches any other `Exception`. It writes `"Unexpected error: {me to record the unhandled exception in event logs and generate a crash dump, providing diagnostics for unexpected failures without suppressing the error. -### Interactions +### Dependencies | Dependency | Usage | | ---------- | ----- | diff --git a/docs/design/nuget-cache-tool/self-test.md b/docs/design/nuget-cache-tool/self-test.md index 35cbb14..f11e563 100644 --- a/docs/design/nuget-cache-tool/self-test.md +++ b/docs/design/nuget-cache-tool/self-test.md @@ -1,5 +1,7 @@ ## SelfTest Subsystem Design +![SelfTest Structure](SelfTestView.svg) + ### Overview The SelfTest subsystem provides self-validation functionality for the NuGet Cache Tool. It @@ -22,13 +24,14 @@ The SelfTest subsystem exposes the following entry point: | Member | Description | | ------ | ----------- | -| `Validation.Run(Context context)` | Executes all self-validation tests and optionally writes results to the file specified by `context.ResultsFile`; writes pass/fail summary via `context.WriteLine` | +| `Validation.Run(Context context)` | Executes all self-validation tests and optionally writes results to the file specified by `context.ResultsFile`; writes the `Total Tests`/`Passed` summary lines via `context.WriteLine`, and the `Failed` summary line via `context.WriteError` when the failure count is greater than zero | The subsystem consumes the following interfaces: | Dependency | Direction | Description | | ---------- | --------- | ----------- | | `Context` (CLI subsystem) | Upstream | Provides `ResultsFile` path and output methods | +| `Program` (top-level unit) | Downstream | `RunValidationTest` calls `Program.Run` in-process to exercise the full program path for each test | | `DemaConsulting.TestResults` (OTS) | Downstream | `TrxSerializer`, `JUnitSerializer` for result output | | `TemporaryDirectory` (Utilities subsystem) | Downstream | `Validation` uses `TemporaryDirectory` for isolated test directories | diff --git a/docs/design/nuget-cache-tool/self-test/validation.md b/docs/design/nuget-cache-tool/self-test/validation.md index 57eef3e..07a99f4 100644 --- a/docs/design/nuget-cache-tool/self-test/validation.md +++ b/docs/design/nuget-cache-tool/self-test/validation.md @@ -1,5 +1,7 @@ ### Validation Unit Design +![SelfTest Structure](SelfTestView.svg) + #### Purpose `Validation` provides a self-validation test framework that executes the tool @@ -8,14 +10,15 @@ work correctly in the deployment environment. #### Data Model -`Validation` accumulates results in a list during execution: +`Validation` accumulates results in a `DemaConsulting.TestResults.TestResults` container during +execution: | Member | Type | Description | | ------ | ---- | ----------- | -| `testResults` (local) | `List` | Accumulated pass/fail records for each test, populated by `RunValidationTest` | +| `testResults` (local) | `DemaConsulting.TestResults.TestResults` | Accumulated pass/fail records for each test, populated by `RunValidationTest` | `Validation` itself is a static class with no persistent instance state; the -`testResults` list is scoped to a single `Run` invocation. +`testResults` container is scoped to a single `Run` invocation. #### Key Methods @@ -24,19 +27,52 @@ work correctly in the deployment environment. Executes all self-validation tests and optionally writes results to the file specified by `context.ResultsFile`. - **Preconditions**: `context` is a valid, non-disposed `Context` -- **Postconditions**: all three tests executed; pass/fail summary written via `context.WriteLine`; results file written if `context.ResultsFile` is non-null and has a supported extension -- **Algorithm**: (1) create `testResults` list; (2) call `RunVersionTest`, `RunHelpTest`, `RunCachePackageTest` unconditionally; (3) write summary; (4) if `ResultsFile` is non-null, write TRX (`.trx`) or JUnit XML (`.xml`) via the appropriate serializer; unsupported extensions call `context.WriteError` +- **Postconditions**: all three tests executed; pass/fail summary written via `context.WriteLine` (the `Failed` line is instead written via `context.WriteError` when the failed count is greater than zero); results file written if `context.ResultsFile` is non-null and has a supported extension +- **Algorithm**: (1) create `testResults` container; (2) call `RunVersionTest`, `RunHelpTest`, `RunCachePackageTest` unconditionally; (3) write summary; (4) if `ResultsFile` is non-null, write TRX (`.trx`) or JUnit XML (`.xml`) via the appropriate serializer; unsupported extensions call `context.WriteError` -##### RunValidationTest(string testName, string[] args, Action\ validator) +##### RunValidationTest(Context context, TestResults testResults, string testName, string displayName, string[] additionalArgs, Func\ validator) Common runner for all three self-validation tests. -- **Preconditions**: `testName` is non-null; `args` is a valid argument array; `validator` is non-null -- **Postconditions**: a `TestResult` (pass or fail) is appended to the shared `testResults` list +- **Preconditions**: `context` and `testResults` are non-null; `testName` and `displayName` are non-null; `additionalArgs` is a valid argument array; `validator` is non-null +- **Postconditions**: a `TestResult` (pass or fail) is appended to the shared `testResults` container - **Algorithm**: (1) create a `TemporaryDirectory` for isolation; (2) obtain log file path via - `tempDir.GetFilePath`; (3) call `Program.Run` in-process with `--silent --log` and `args`; + `tempDir.GetFilePath`; (3) call `Program.Run` in-process with `--silent --log` and `additionalArgs`; (4) invoke `validator` with captured log content; (5) record pass or fail +##### RunCachePackageTest(Context context, TestResults testResults) + +Runs the cache-package self-validation test: caches a known package/version and +verifies the tool reports a real, correctly-identified cached package path. + +- **Preconditions**: `context` and `testResults` are non-null +- **Postconditions**: a `TestResult` (pass or fail) is appended to `testResults` +- **Algorithm**: (1) call `RunValidationTest` with the known package identity + (`CachePackageTestId`:`CachePackageTestVersion`); (2) the validator extracts the + cached package path from the last non-blank log line; (3) verifies the path + exists on disk via `Directory.Exists`; (4) calls `ValidateCachePackagePath` to + confirm the path is named for the exact requested package identity + +##### ValidateCachePackagePath(string packagePath, string expectedPackageId, string expectedVersion) + +Verifies that a resolved cached package directory path is named for the exact +expected package ID and version, guarding against false positives from substring +matching (for example, expected version `0.1.0` must not match a path segment of +`0.1.0-beta` or `10.1.0`). + +- **Preconditions**: `packagePath`, `expectedPackageId`, and `expectedVersion` are non-null +- **Postconditions**: returns `null` if the path's directory name equals + `expectedVersion` and its parent directory name equals `expectedPackageId` + (both case-insensitive); otherwise returns a descriptive error message +- **Algorithm**: (1) trim trailing path separators from `packagePath`; (2) take the + final path segment as the version directory name; (3) take its parent segment as + the package ID directory name; (4) compare both segments to the expected values + with `StringComparison.OrdinalIgnoreCase`, exactly rather than by substring +- **Rationale**: extracted as its own `internal` method (rather than an inline + check within `RunCachePackageTest`) so unit tests can exercise it directly + against known-good and known-bad paths, proving the check itself would catch a + regression rather than only proving the overall self-test happens to pass today + #### Test Structure Three tests are executed unconditionally: @@ -45,7 +81,7 @@ Three tests are executed unconditionally: | ---- | --------- | | `RunVersionTest` | `--version` flag outputs the version string | | `RunHelpTest` | `--help` flag outputs usage information | -| `RunCachePackageTest` | Caching a known package produces a valid path | +| `RunCachePackageTest` | Caching a known package produces a valid, correctly-identified path (exact directory/parent-directory match via `ValidateCachePackagePath`, not substring matching) | #### RunValidationTest Pattern @@ -55,7 +91,7 @@ Three tests are executed unconditionally: 2. Obtains a log file path via `tempDir.GetFilePath` 3. Launches the tool with additional arguments and captures the log 4. Calls the caller-supplied `validator` delegate to check output -5. Records pass/fail in the shared `testResults` list +5. Records pass/fail in the shared `testResults` container #### Results File Writing @@ -66,7 +102,7 @@ After all tests complete, `Validation.Run` writes the results file if - `.xml` extension → serialized using `JUnitSerializer` - any other extension → treated as an error (unsupported results file extension) -#### Interactions +#### Dependencies | Dependency | Usage | | ---------- | ----- | @@ -74,7 +110,12 @@ After all tests complete, `Validation.Run` writes the results file if | `PathHelpers` (Utilities subsystem) | `SafePathCombine` constructs temp log file paths via `TemporaryDirectory.GetFilePath` | | `TemporaryDirectory` | Used by `RunValidationTest` to create and manage isolated temporary directories for each test scenario | | `Program` | Called in-process; `RunValidationTest` calls `Program.Run(testContext)` for each test | -| `DemaConsulting.TestResults` | `TrxSerializer`, `JUnitSerializer` for result output | +| `DemaConsulting.TestResults` (OTS) | `TrxSerializer`, `JUnitSerializer` for result output | + +#### Callers + +`Program.Run` calls `Validation.Run(context)` when `context.Validate` is true. There are no +other callers of `Validation` in production code. #### Error Handling diff --git a/docs/design/nuget-cache-tool/utilities.md b/docs/design/nuget-cache-tool/utilities.md index 45791e7..5a296f2 100644 --- a/docs/design/nuget-cache-tool/utilities.md +++ b/docs/design/nuget-cache-tool/utilities.md @@ -1,5 +1,7 @@ ## NuGet Cache Tool Utilities Subsystem Design +![Utilities Structure](UtilitiesView.svg) + ### Overview The Utilities subsystem provides shared helper functionality used by production code and diff --git a/docs/design/nuget-cache-tool/utilities/path-helpers.md b/docs/design/nuget-cache-tool/utilities/path-helpers.md index 9170c53..40863a6 100644 --- a/docs/design/nuget-cache-tool/utilities/path-helpers.md +++ b/docs/design/nuget-cache-tool/utilities/path-helpers.md @@ -1,5 +1,7 @@ ### PathHelpers Unit Design +![Utilities Structure](UtilitiesView.svg) + #### Purpose `PathHelpers` provides safe path combination utilities that prevent path traversal @@ -17,8 +19,13 @@ behavior is encapsulated in the pure static method `SafePathCombine`. Combines `basePath` and `relativePath`, rejecting any result that escapes the base directory. - **Preconditions**: both arguments are non-null -- **Postconditions**: returned path is within `basePath`; throws on traversal or absolute override -- **Algorithm**: (1) reject null inputs via `ArgumentNullException.ThrowIfNull`; (2) combine paths with `Path.Combine`; (3) resolve both `basePath` and combined path to absolute form using `Path.GetFullPath`; (4) call `Path.GetRelativePath(absoluteBase, absoluteCombined)` and reject if result is `".."`, starts with `".."`+separator, or is itself rooted +- **Postconditions**: returned path is within `basePath`; throws on a rooted `relativePath`, + traversal, or absolute override +- **Algorithm**: (1) reject null inputs via `ArgumentNullException.ThrowIfNull`; (2) reject a + rooted `relativePath` outright via `Path.IsPathRooted`, before any combination occurs; (3) + combine paths with `Path.Combine`; (4) resolve both `basePath` and combined path to absolute + form using `Path.GetFullPath`; (5) call `Path.GetRelativePath(absoluteBase, absoluteCombined)` + and reject if result is `".."`, starts with `".."`+separator, or is itself rooted #### SafePathCombine Algorithm @@ -26,11 +33,16 @@ Combines `basePath` and `relativePath`, rejecting any result that escapes the ba 1. **Reject null inputs**: throws `ArgumentNullException` via `ArgumentNullException.ThrowIfNull` if either argument is `null` -2. **Combine paths**: calls `Path.Combine(basePath, relativePath)` to produce the +2. **Reject rooted `relativePath` upfront**: calls `Path.IsPathRooted(relativePath)` and + throws `ArgumentException` immediately if `relativePath` is rooted (absolute), regardless + of where it would resolve to. This check runs before any combination or resolution occurs, + so a rooted `relativePath` is always rejected even when it happens to resolve inside + `basePath`. +3. **Combine paths**: calls `Path.Combine(basePath, relativePath)` to produce the candidate path (preserving the caller's relative/absolute style) -3. **Resolve to absolute form**: calls `Path.GetFullPath` on both `basePath` and the +4. **Resolve to absolute form**: calls `Path.GetFullPath` on both `basePath` and the combined path -4. **GetRelativePath containment check**: calls +5. **GetRelativePath containment check**: calls `Path.GetRelativePath(absoluteBase, absoluteCombined)` and rejects the input if the result is exactly `".."`, starts with `".."` followed by `Path.DirectorySeparatorChar` or `Path.AltDirectorySeparatorChar`, or is itself rooted (absolute), which would @@ -41,7 +53,7 @@ Combines `basePath` and `relativePath`, rejecting any result that escapes the ba | Property | Guarantee | | -------- | --------- | | No parent traversal | Post-combine `GetRelativePath` check detects any traversal that escapes the base directory | -| No absolute override | Escape detected via `GetRelativePath` `..` prefix (same-root) or rooted result (cross-root) | +| No absolute override | An upfront `Path.IsPathRooted(relativePath)` check rejects any rooted `relativePath` outright, even one that would resolve inside `basePath` | | Canonicalization check | `GetFullPath` normalizes paths; `GetRelativePath` checks `..`, `..`+sep, or rooted | | Valid names with `..` prefix | Names like `..data` stay within the base and are correctly accepted | @@ -52,9 +64,15 @@ Combines `basePath` and `relativePath`, rejecting any result that escapes the ba directory-separator normalization natively. The containment test treats `..` as an escaping segment only when it is the entire relative result or is followed by a directory separator, avoiding false positives for valid in-base names such as `..data`. -- **Post-combine canonical-path check**: Resolving paths after combining handles all traversal - patterns — `../`, embedded `/../`, absolute-path overrides, and platform edge cases — - without fragile pre-combine string inspection of `relativePath`. +- **Upfront rejection of rooted `relativePath`**: An absolute `relativePath` is rejected + before any combination or resolution occurs, so a rooted input is always rejected even when + it would resolve inside `basePath` after combination (for example, `basePath="C:\base"` and + `relativePath="C:\base\foo"`). This closes a gap that a purely post-combine containment + check cannot detect, since `Path.Combine` discards `basePath` entirely when `relativePath` + is rooted. +- **Post-combine canonical-path check**: Resolving paths after combining handles the + remaining traversal patterns — `../` and embedded `/../` — without fragile pre-combine + string inspection of `relativePath`. - **Specific exception for containment-check failure**: When the post-combine `GetRelativePath` containment check determines the combined path escapes the base directory, `SafePathCombine` throws a specific `ArgumentException` identifying @@ -62,12 +80,6 @@ Combines `basePath` and `relativePath`, rejecting any result that escapes the ba - **No logging or error accumulation**: `SafePathCombine` is a pure utility method that throws on invalid input; it does not interact with the `Context` or any output mechanism. -#### Interactions - -- **Called by `TemporaryDirectory`**: enforces the directory boundary in `GetFilePath` -- **Called by `Validation`** (SelfTest subsystem): constructs log file paths inside temporary - directories via `TemporaryDirectory.GetFilePath` - #### Error Handling | Scenario | Exception thrown | @@ -75,8 +87,8 @@ Combines `basePath` and `relativePath`, rejecting any result that escapes the ba | `basePath` is null | `ArgumentNullException` (parameter: `basePath`) | | `relativePath` is null | `ArgumentNullException` (parameter: `relativePath`) | | `relativePath` is empty | Returns `basePath` unchanged (no exception) | +| `relativePath` is rooted (absolute), regardless of whether it resolves inside `basePath` | `ArgumentException` (parameter: `relativePath`) | | Combined path escapes the base directory | `ArgumentException` (parameter: `relativePath`) | -| Combined path is absolute (cross-root override) | `ArgumentException` (parameter: `relativePath`) | All errors are reported by throwing immediately; no partial state is produced and no error accumulation occurs. @@ -85,3 +97,10 @@ no error accumulation occurs. `PathHelpers` depends exclusively on `System.IO.Path` from the .NET BCL. It has no dependency on other units, subsystems, or third-party packages. + +#### Callers + +- **`TemporaryDirectory`**: calls `SafePathCombine` to enforce the directory boundary in + `GetFilePath` +- **`Validation`** (SelfTest subsystem): constructs log file paths inside temporary + directories via `TemporaryDirectory.GetFilePath`, which relies on `SafePathCombine` diff --git a/docs/design/nuget-cache-tool/utilities/temporary-directory.md b/docs/design/nuget-cache-tool/utilities/temporary-directory.md index 508ef25..bfc9bda 100644 --- a/docs/design/nuget-cache-tool/utilities/temporary-directory.md +++ b/docs/design/nuget-cache-tool/utilities/temporary-directory.md @@ -1,5 +1,7 @@ ### TemporaryDirectory Design +![Utilities Structure](UtilitiesView.svg) + The `TemporaryDirectory` class provides a disposable temporary directory that is automatically created on construction and deleted on disposal. It is used by both production self-test code (`Validation`) and by unit and integration test infrastructure. @@ -36,7 +38,12 @@ Creates a uniquely-named subdirectory under `Environment.CurrentDirectory`. **Throws:** - `InvalidOperationException` — wraps any `IOException`, `UnauthorizedAccessException`, or - `ArgumentException` raised by `Directory.CreateDirectory`. + `ArgumentException` raised by `Directory.CreateDirectory`. This is a defensive guard against + file-system failures (for example, permission restrictions or an unwritable working + directory); it is not covered by a dedicated automated test because reliably forcing + `Directory.CreateDirectory` to fail requires environment-specific, OS-level permission + manipulation (for example, Windows `icacls`) that is itself flaky and platform-fragile. The + wrapping behavior is reviewed by code inspection instead. ##### GetFilePath(string relativePath) → string diff --git a/docs/design/ots.md b/docs/design/ots.md index 01f7033..0d16c16 100644 --- a/docs/design/ots.md +++ b/docs/design/ots.md @@ -17,6 +17,12 @@ Both `DemaConsulting.NuGet.Caching` and `DemaConsulting.TestResults` satisfy all they are purpose-built libraries published by DEMA Consulting under the MIT License, each exposing a minimal, stable API that meets the NuGet Cache Tool's exact needs. +`SysML2Tools` is a build-time dotnet local tool rather than a runtime NuGet package dependency. It +is selected against the same criteria: it is published by DEMA Consulting, licensed compatibly, and +exposes a minimal command-line surface (`lint`, `render`, and `query`) with no wrapper code +required. The CI pipeline invokes `lint` and `render`; the `query` subcommand is used interactively +by AI agents (see the `sysml2tools-query` skill) rather than by any automated pipeline step. + ## Version Management Policy OTS package versions are declared as exact NuGet package references in the project file @@ -26,11 +32,16 @@ raises pull requests for version upgrades. Each upgrade pull request is evaluate CI/CD test suite before merging; major-version upgrades additionally require a design review to confirm that the API surface used by the NuGet Cache Tool remains compatible. +`SysML2Tools` is pinned to an exact version in `.config/dotnet-tools.json` +(`demaconsulting.sysml2tools.tool`) and restored with `dotnet tool restore` at the start of each +CI job, providing the same reproducibility and audit trail as the other pipeline tools. + ## General Integration Approach -Both OTS items are consumed as NuGet packages declared in the project file. No wrapper classes or -abstraction interfaces are introduced; OTS APIs are called directly from the units that require -them. This keeps the integration surface minimal and directly traceable from source to design: +Both `DemaConsulting.NuGet.Caching` and `DemaConsulting.TestResults` are consumed as NuGet packages +declared in the project file. No wrapper classes or abstraction interfaces are introduced; OTS +APIs are called directly from the units that require them. This keeps the integration surface +minimal and directly traceable from source to design: - `DemaConsulting.NuGet.Caching` is called directly from `Program.RunToolLogic` - `DemaConsulting.TestResults` serializers are called directly from `Validation.Run` @@ -38,11 +49,16 @@ them. This keeps the integration surface minimal and directly traceable from sou Error handling follows the conventions of the calling unit: exceptions thrown by OTS APIs are caught at the call site and reported via `context.WriteError`, which sets the process exit code to 1. +`SysML2Tools` is a dotnet local tool invoked as a command-line executable from `lint.ps1` and +`build.yaml`; no wrapper code is written, and it is not referenced by the main project file. A +non-zero exit code from a `sysml2tools` invocation fails the build immediately, consistent with +the GitHub Actions default `fail-fast` behavior. + ## Qualification Strategy OTS items are qualified through integration-test evidence produced by the CI/CD pipeline. The -NuGet Cache Tool's self-validation tests (`--validate`) exercise both OTS items end-to-end in -the deployment environment: +NuGet Cache Tool's self-validation tests (`--validate`) exercise the two runtime OTS items +end-to-end in the deployment environment: - `DemaConsulting.NuGet.Caching` is exercised by `NuGetCache_CachePackage`, which calls `NuGetCache.EnsureCachedAsync` against the live NuGet feed @@ -53,22 +69,32 @@ Self-validation results are emitted as machine-readable test result files (TRX o consumed by the ReqStream traceability tool, providing traceable OTS qualification evidence. See each OTS item's verification document for the specific test scenarios and pass/fail criteria. +`SysML2Tools` is qualified through its own built-in self-validation mode +(`dotnet sysml2tools --validate --results `), which exercises `lint` and +`render --format svg` against known model fixtures and emits a TRX file consumed by ReqStream. +The pipeline additionally exercises `SysML2Tools` against the real NuGetCacheTool model via +`lint.ps1` and the build-docs job's render step — see *SysML2Tools OTS Design* and +*SysML2Tools Verification*. + ## Integration Strategy -The NuGet Cache Tool integrates two OTS (off-the-shelf) software items: +The NuGet Cache Tool integrates three OTS (off-the-shelf) software items: | OTS Item | Package | Purpose | | -------- | ------- | ------- | | DemaConsulting.NuGet.Caching | `DemaConsulting.NuGet.Caching` | NuGet package caching to the global packages folder | | DemaConsulting.TestResults | `DemaConsulting.TestResults` | Serialization of self-validation results to TRX and JUnit XML formats | +| SysML2Tools | `demaconsulting.sysml2tools.tool` | Validates the SysML2 architecture model and renders its declared views to SVG diagrams | -Both OTS items are consumed as NuGet packages declared in the main project file +Both `DemaConsulting.NuGet.Caching` and `DemaConsulting.TestResults` are consumed as NuGet packages +declared in the main project file (`src/DemaConsulting.NuGet.CacheTool/DemaConsulting.NuGet.CacheTool.csproj`). No wrapper layers or abstraction interfaces are introduced; the OTS APIs are called -directly from the units that need them. +directly from the units that need them. `SysML2Tools` is instead installed as a dotnet local +tool via `.config/dotnet-tools.json` and invoked directly from `lint.ps1` and `build.yaml`. OTS items are verified by integration-test evidence from the CI/CD pipeline. Each OTS item's verification document lists the integration test scenarios that demonstrate the item functions correctly in the deployment environment — see -*DemaConsulting.NuGet.Caching Integration Design* and -*DemaConsulting.TestResults Integration Design*. +*DemaConsulting.NuGet.Caching Integration Design*, *DemaConsulting.TestResults Integration Design*, +and *SysML2Tools OTS Design*. diff --git a/docs/design/ots/nuget-caching.md b/docs/design/ots/nuget-caching.md index 0e168cb..c7b84eb 100644 --- a/docs/design/ots/nuget-caching.md +++ b/docs/design/ots/nuget-caching.md @@ -1,13 +1,13 @@ ## DemaConsulting.NuGet.Caching Integration Design -### Why Chosen +### Purpose `DemaConsulting.NuGet.Caching` is the purpose-built library for caching NuGet packages to the global packages folder. It provides a single async API — `NuGetCache.EnsureCachedAsync` — that abstracts all NuGet feed querying, download, and extraction logic. Using this library avoids re-implementing NuGet client protocol handling within the NuGet Cache Tool itself. -### APIs Used +### Features Used | API | Signature | Usage | | --- | --------- | ----- | @@ -21,8 +21,6 @@ application with no synchronization context that could cause a deadlock. The ret is written to output via `context.WriteLine`. Any `InvalidOperationException` thrown by the library is caught and reported via `context.WriteError`. -### Version Constraints - The specific version of `DemaConsulting.NuGet.Caching` is declared in the project file. Compatibility is verified by the integration test suite, which exercises the `NuGetCache.EnsureCachedAsync` API end-to-end against the live NuGet feed. diff --git a/docs/design/ots/sysml2tools.md b/docs/design/ots/sysml2tools.md new file mode 100644 index 0000000..59fc097 --- /dev/null +++ b/docs/design/ots/sysml2tools.md @@ -0,0 +1,59 @@ +## SysML2Tools OTS Design + +DemaConsulting.SysML2Tools is a .NET local tool that lints a SysML2 architecture model +and renders its declared views to SVG diagrams consumed by the design documentation. + +### Purpose + +SysML2Tools provides the toolchain that keeps NuGetCacheTool's architecture model +(`docs/sysml2/model/**/*.sysml`) and its declared views (`docs/sysml2/views/design-views.sysml`) +consistent with the design documentation. It validates the model for syntax and reference errors, +and renders each declared view to an SVG diagram embedded directly in the corresponding design +document (for example `docs/design/introduction.md` embeds `SoftwareStructureView.svg`). AI agents +also query the model directly via the `sysml2tools-query` skill rather than parsing the rendered +diagrams or hand-maintained prose. + +SysML2Tools is chosen because it is the reference implementation for the SysML v2 subset used by +this project's Continuous Compliance methodology, and it integrates directly with the same +dotnet-tool restore and CI pipeline used by the other pipeline tools. + +### Features Used + +- Model validation via `dotnet sysml2tools lint 'docs/sysml2/**/*.sysml'`, which reports syntax + errors and unresolved references without producing output files. +- View rendering via `dotnet sysml2tools render`, which renders every `view` declared in + `design-views.sysml` to an SVG file named after the view (for example + `SoftwareStructureView.svg`). The full command is: + + ```text + dotnet sysml2tools render --output docs/design/generated --format svg \ + 'docs/sysml2/model/**/*.sysml' 'docs/sysml2/views/design-views.sysml' + ``` + +- Model querying via the `sysml2tools-query` skill, used by AI agents to answer structural and + traceability questions about the model without parsing the raw SysML2 source or the rendered + diagrams. + +### Integration Pattern + +SysML2Tools is installed as a .NET local tool defined in `.config/dotnet-tools.json` under the +package name `demaconsulting.sysml2tools.tool` and restored with `dotnet tool restore`. It operates +directly on the SysML2 source files under `docs/sysml2/`; no separate configuration file is +required. + +It is used in two places in the pipeline: + +- **Lint** (`lint.ps1`, all CI jobs and local pre-PR checks): `dotnet sysml2tools lint + 'docs/sysml2/**/*.sysml'` fails the build if the model contains syntax errors or unresolved + references. +- **Render** (`build.yaml`, build-docs job): `dotnet sysml2tools render` renders each declared view + to an SVG file in `docs/design/generated/`, immediately before Pandoc compiles the Design + document. The rendered SVGs sit alongside the compiled `design.html`, so the design Markdown + sources embed bare filenames (for example `![Software Structure](SoftwareStructureView.svg)`) + that Pandoc and the browser resolve relative to that directory. + +SysML2Tools reads only the local SysML2 model files. Rendering writes only local SVG diagram +files, while self-validation (`--validate --results`) additionally writes a local TRX results +file consumed by ReqStream; in both modes it requires no external service or network access, and +it has no transitive NuGet dependencies that propagate to the main source project. It is a +build-time tool only. diff --git a/docs/design/ots/test-results.md b/docs/design/ots/test-results.md index 247a99a..627772c 100644 --- a/docs/design/ots/test-results.md +++ b/docs/design/ots/test-results.md @@ -1,33 +1,31 @@ ## DemaConsulting.TestResults Integration Design -### Why Chosen +### Purpose `DemaConsulting.TestResults` provides standard test result serialization in TRX (MSTest) and JUnit XML formats. Using this library allows the NuGet Cache Tool's self-validation results to be consumed by CI/CD systems and requirements traceability tools (such as ReqStream) that expect standard test result file formats. -### APIs Used +### Features Used | API | Signature | Usage | | --- | --------- | ----- | -| `TrxSerializer` | `void Serialize(string path, IEnumerable results)` | Called by `Validation.Run` when `context.ResultsFile` has a `.trx` extension | -| `JUnitSerializer` | `void Serialize(string path, IEnumerable results)` | Called by `Validation.Run` when `context.ResultsFile` has a `.xml` extension | +| `TrxSerializer` | `string Serialize(TestResults results)` | Called by `Validation.Run` when `context.ResultsFile` has a `.trx` extension; the returned XML content is then written to disk via `File.WriteAllText` | +| `JUnitSerializer` | `string Serialize(TestResults results)` | Called by `Validation.Run` when `context.ResultsFile` has a `.xml` extension; the returned XML content is then written to disk via `File.WriteAllText` | ### Integration Pattern After all self-validation tests complete, `Validation.Run` checks `context.ResultsFile`. If non-null, the file extension determines which serializer is used: -- `.trx` → `TrxSerializer` produces an MSTest-compatible TRX XML file -- `.xml` → `JUnitSerializer` produces a JUnit-compatible XML file +- `.trx` → `TrxSerializer.Serialize` returns MSTest-compatible TRX XML content, written to disk via `File.WriteAllText` +- `.xml` → `JUnitSerializer.Serialize` returns JUnit-compatible XML content, written to disk via `File.WriteAllText` - any other extension → `context.WriteError` is called; no file is written The serialized results are self-contained test result files that can be consumed directly by CI/CD pipelines and by the ReqStream traceability tool. -### Version Constraints - The specific version of `DemaConsulting.TestResults` is declared in the project file. Compatibility is verified by the integration test suite, which exercises both serializers end-to-end and asserts the structure of the produced files. diff --git a/docs/reqstream/nuget-cache-tool.yaml b/docs/reqstream/nuget-cache-tool.yaml index 6d1372a..04bd69c 100644 --- a/docs/reqstream/nuget-cache-tool.yaml +++ b/docs/reqstream/nuget-cache-tool.yaml @@ -61,7 +61,7 @@ sections: - NuGetCacheTool_PackageCaching_ValidPackageProvided_OutputsPath - id: NuGetCache-Sys-PlatformSupport - title: The NuGet Cache Tool shall build and run on Windows, Linux, and macOS with .NET 8, .NET 9, and .NET 10. + title: The NuGet Cache Tool shall run on Windows, Linux, and macOS with .NET 8, .NET 9, and .NET 10. justification: | Supporting multiple operating systems and .NET runtimes ensures the tool is usable across the broadest range of developer workstations and CI/CD @@ -129,13 +129,16 @@ sections: - id: NuGetCache-Sys-InvalidArguments title: >- - The NuGet Cache Tool shall reject unknown or malformed arguments - with a descriptive error and non-zero exit code. + The NuGet Cache Tool shall reject an unknown command-line argument with a descriptive error. justification: | Prevents silent misconfiguration by providing clear diagnostics when the tool is invoked incorrectly, enabling users to identify and correct usage errors quickly. + Non-zero exit code behavior on error is independently covered by + NuGetCache-Sys-ExitCode, so it is not restated here. children: - - NuGetCache-Cli-InvalidArguments + - NuGetCache-Cli-InvalidArguments-UnknownFlag + - NuGetCache-Cli-InvalidArguments-MissingLogValue + - NuGetCache-Cli-InvalidArguments-MissingResultsValue tests: - NuGetCacheTool_ErrorHandling_UnknownArgumentProvided_ReturnsError @@ -158,6 +161,11 @@ sections: justification: | Ensures that callers and CI/CD pipelines can diagnose failure conditions via stderr output. Silent mode suppresses all console output including error messages. + `Runner.Run` merges stdout and stderr, so the system-level tests below can only + confirm that error output appears somewhere in the combined stream; direct proof + that the message specifically targets stderr is carried by the child requirements + (see *CLI Subsystem Verification* and *Program Verification*), whose unit tests + capture stdout and stderr separately. children: - NuGetCache-Cli-ErrorOutput - NuGetCache-Program-ErrorOutput @@ -171,11 +179,15 @@ sections: justification: | When --silent is active, the log file remains the only persistent record of tool execution. Preserving log file writes in silent mode ensures output is always - captured for audit and debugging purposes. + captured for audit and debugging purposes. The combined --silent plus --log + interaction is proven precisely at the CLI subsystem level (see + NuGetCache-Cli-SilentLogInteraction and *CLI Subsystem Verification*), because + `Runner.Run` cannot isolate this interaction from combined console output at the + system level. children: - NuGetCache-Cli-SilentLogInteraction tests: - - NuGetCacheTool_LogFile_LogFlagProvided_WritesOutputToFile + - Cli_SilentAndLog_WritesToLogFileOnly - id: NuGetCache-Sys-PathSafety title: >- @@ -199,6 +211,12 @@ sections: Idempotent operation is essential for CI/CD pipelines where the same command may be re-run. The tool must not treat an already-cached package as an error, allowing pipelines to be safely retried or re-executed without manual cleanup. + The system-level test below exercises the same package-caching path regardless of + whether the package was already present in the cache from a prior run, so it does + not by itself isolate the already-cached case; precise coverage of the caching + call itself is carried by NuGetCache-Program-CachePackages (see *Program + Verification*) together with the idempotent behavior of `NuGetCache.EnsureCachedAsync` + documented in *DemaConsulting.NuGet.Caching Integration Design*. children: - NuGetCache-Program-CachePackages tests: diff --git a/docs/reqstream/nuget-cache-tool/cli.yaml b/docs/reqstream/nuget-cache-tool/cli.yaml index cc5e566..88758be 100644 --- a/docs/reqstream/nuget-cache-tool/cli.yaml +++ b/docs/reqstream/nuget-cache-tool/cli.yaml @@ -52,7 +52,7 @@ sections: - Cli_PackageArgument_AddedToPackagesList - id: NuGetCache-Cli-ErrorOutput - title: The CLI shall write error messages to the output stream when an error occurs. + title: The CLI shall write error messages to stderr when an error occurs. justification: | Users and automated callers must receive a descriptive error message so they can diagnose the failure condition without inspecting internal state. @@ -101,16 +101,34 @@ sections: tests: - Cli_ResultsFlag_SetsResultsFileOnContext - - id: NuGetCache-Cli-InvalidArguments - title: The CLI shall reject unknown or malformed command-line arguments with a descriptive error. + - id: NuGetCache-Cli-InvalidArguments-UnknownFlag + title: The CLI shall reject an unknown command-line flag with a descriptive error. justification: | - Providing clear feedback for invalid arguments helps users quickly correct + Providing clear feedback for an unrecognized flag helps users quickly correct mistakes and prevents silent misconfiguration. children: - - NuGetCache-Context-InvalidArguments + - NuGetCache-Context-InvalidArguments-UnknownFlag tests: - Cli_UnknownArgument_ThrowsArgumentException + + - id: NuGetCache-Cli-InvalidArguments-MissingLogValue + title: The CLI shall reject the --log flag when no file path value follows it. + justification: | + Providing clear feedback when --log is supplied without a value helps users + quickly correct mistakes and prevents silent misconfiguration. + children: + - NuGetCache-Context-InvalidArguments-MissingLogValue + tests: - Cli_LogFlagWithoutValue_ThrowsArgumentException + + - id: NuGetCache-Cli-InvalidArguments-MissingResultsValue + title: The CLI shall reject the --results flag when no file path value follows it. + justification: | + Providing clear feedback when --results is supplied without a value helps users + quickly correct mistakes and prevents silent misconfiguration. + children: + - NuGetCache-Context-InvalidArguments-MissingResultsValue + tests: - Cli_ResultsFlagWithoutValue_ThrowsArgumentException - id: NuGetCache-Cli-SilentLogInteraction diff --git a/docs/reqstream/nuget-cache-tool/cli/context.yaml b/docs/reqstream/nuget-cache-tool/cli/context.yaml index 47096ed..1faf146 100644 --- a/docs/reqstream/nuget-cache-tool/cli/context.yaml +++ b/docs/reqstream/nuget-cache-tool/cli/context.yaml @@ -66,13 +66,35 @@ sections: tests: - Context_WriteError_SetsErrorExitCode - - id: NuGetCache-Context-InvalidArguments - title: The Context class shall reject unknown or malformed command-line arguments with a descriptive error. + - id: NuGetCache-Context-InvalidArguments-UnknownFlag + title: The Context class shall reject an unknown command-line flag with a descriptive error. justification: | - Providing clear feedback for invalid arguments helps users quickly correct + Providing clear feedback for an unrecognized flag helps users quickly correct mistakes and prevents silent misconfiguration. tests: - Context_Create_UnknownArgument_ThrowsArgumentException + + - id: NuGetCache-Context-InvalidArguments-MissingLogValue + title: The Context class shall reject the --log flag when no file path value follows it. + justification: | + Providing clear feedback when --log is supplied without a value helps users + quickly correct mistakes and prevents silent misconfiguration. + tests: - Context_Create_LogFlag_WithoutValue_ThrowsArgumentException + + - id: NuGetCache-Context-InvalidArguments-MissingResultsValue + title: The Context class shall reject the --results flag when no file path value follows it. + justification: | + Providing clear feedback when --results is supplied without a value helps users + quickly correct mistakes and prevents silent misconfiguration. + tests: - Context_Create_ResultsFlag_WithoutValue_ThrowsArgumentException + + - id: NuGetCache-Context-InvalidArguments-MalformedPackage + title: The Context class shall reject a package argument that does not contain a colon separator. + justification: | + Users may accidentally supply a package argument without the required colon + separator. Providing clear feedback for this specific malformed-argument case + helps users correct their input without inspecting source code or documentation. + tests: - Context_Create_WithoutColonInPackage_ThrowsArgumentException diff --git a/docs/reqstream/nuget-cache-tool/platform-requirements.yaml b/docs/reqstream/nuget-cache-tool/platform-requirements.yaml index fc88179..6e3e56c 100644 --- a/docs/reqstream/nuget-cache-tool/platform-requirements.yaml +++ b/docs/reqstream/nuget-cache-tool/platform-requirements.yaml @@ -9,7 +9,7 @@ sections: - title: Platform Support requirements: - id: NuGetCache-PLT-Windows - title: The tool shall build and run on Windows platforms. + title: The tool shall run on Windows platforms. justification: | DEMA Consulting tools must support Windows as a major development platform. tests: @@ -18,7 +18,7 @@ sections: - "windows@NuGetCache_HelpDisplay" - id: NuGetCache-PLT-Linux - title: The tool shall build and run on Linux platforms. + title: The tool shall run on Linux platforms. justification: | DEMA Consulting tools must support Linux for CI/CD and containerized environments. tests: @@ -27,7 +27,7 @@ sections: - "ubuntu@NuGetCache_HelpDisplay" - id: NuGetCache-PLT-MacOS - title: The tool shall build and run on macOS platforms. + title: The tool shall run on macOS platforms. justification: | DEMA Consulting tools must support macOS for developers using Apple platforms. tests: diff --git a/docs/reqstream/nuget-cache-tool/program.yaml b/docs/reqstream/nuget-cache-tool/program.yaml index 21c0d8f..2e2c7ec 100644 --- a/docs/reqstream/nuget-cache-tool/program.yaml +++ b/docs/reqstream/nuget-cache-tool/program.yaml @@ -70,7 +70,7 @@ sections: Validation is performed by Context.Create before packages reach RunToolLogic, with a defensive check inside RunToolLogic for belt-and-suspenders safety. children: - - NuGetCache-Context-InvalidArguments + - NuGetCache-Context-InvalidArguments-MalformedPackage tests: - Context_Create_WithoutColonInPackage_ThrowsArgumentException diff --git a/docs/reqstream/nuget-cache-tool/self-test/validation.yaml b/docs/reqstream/nuget-cache-tool/self-test/validation.yaml index f38c890..ac994d7 100644 --- a/docs/reqstream/nuget-cache-tool/self-test/validation.yaml +++ b/docs/reqstream/nuget-cache-tool/self-test/validation.yaml @@ -18,6 +18,13 @@ sections: - NuGetCache_VersionDisplay - NuGetCache_HelpDisplay - NuGetCache_CachePackage + - Validation_Run_CachePackageSelfTest_PassesWithRealCachedPackagePath + - Validation_ValidateCachePackagePath_ExactMatch_ReturnsNull + - Validation_ValidateCachePackagePath_VersionSuffixSubstringMatch_ReturnsError + - Validation_ValidateCachePackagePath_VersionPrefixSubstringMatch_ReturnsError + - Validation_ValidateCachePackagePath_WrongPackageId_ReturnsError + - Validation_ValidateCachePackagePath_NoParentDirectory_ReturnsError + - Validation_ValidateCachePackagePath_BareRelativeName_ReturnsError - id: NuGetCache-Validation-ResultsFile title: The Validation class shall write validation results to a file in TRX or JUnit format. diff --git a/docs/reqstream/nuget-cache-tool/utilities.yaml b/docs/reqstream/nuget-cache-tool/utilities.yaml index 3bbe850..3872d17 100644 --- a/docs/reqstream/nuget-cache-tool/utilities.yaml +++ b/docs/reqstream/nuget-cache-tool/utilities.yaml @@ -29,7 +29,8 @@ sections: - NuGetCache-TemporaryDirectory-DirectoryCreation - NuGetCache-TemporaryDirectory-FilePathResolution - NuGetCache-TemporaryDirectory-TraversalRejection - - NuGetCache-TemporaryDirectory-Disposal + - NuGetCache-TemporaryDirectory-Disposal-DeletesContents + - NuGetCache-TemporaryDirectory-Disposal-IdempotentWhenMissing tests: - Utilities_PathResolution_ValidRelativePath_ReturnsPathWithinDirectory - Utilities_PathResolution_NestedRelativePath_CreatesIntermediateDirectories diff --git a/docs/reqstream/nuget-cache-tool/utilities/temporary-directory.yaml b/docs/reqstream/nuget-cache-tool/utilities/temporary-directory.yaml index 6138ea3..90bb798 100644 --- a/docs/reqstream/nuget-cache-tool/utilities/temporary-directory.yaml +++ b/docs/reqstream/nuget-cache-tool/utilities/temporary-directory.yaml @@ -2,48 +2,59 @@ # NuGet Cache Tool TemporaryDirectory Unit Requirements sections: - - title: TemporaryDirectory Unit Requirements - requirements: - - id: NuGetCache-TemporaryDirectory-DirectoryCreation - title: The TemporaryDirectory class shall create a unique temporary directory under the current working directory - on construction. - justification: | - Using Environment.CurrentDirectory as the base — rather than Path.GetTempPath() — - avoids the macOS /tmp → /private/tmp symlink issue that causes path-comparison - failures. A GUID-derived name guarantees uniqueness so concurrent test runs do not - collide. - tests: - - TemporaryDirectory_Constructor_CreatesDirectory - - TemporaryDirectory_Constructor_CreatesUniqueDirectories + - title: NuGet Cache Tool Requirements + sections: + - title: Utilities Subsystem Requirements + sections: + - title: TemporaryDirectory Unit Requirements + requirements: + - id: NuGetCache-TemporaryDirectory-DirectoryCreation + title: The TemporaryDirectory class shall create a unique temporary directory under the current working directory + on construction. + justification: | + Using Environment.CurrentDirectory as the base — rather than Path.GetTempPath() — + avoids the macOS /tmp → /private/tmp symlink issue that causes path-comparison + failures. A GUID-derived name guarantees uniqueness so concurrent test runs do not + collide. + tests: + - TemporaryDirectory_Constructor_WhenCalled_CreatesDirectoryOnDisk + - TemporaryDirectory_Constructor_MultipleInstances_CreatesUniqueDirectories - - id: NuGetCache-TemporaryDirectory-FilePathResolution - title: The TemporaryDirectory class shall resolve relative paths to absolute paths within the temporary directory, - creating any required intermediate subdirectories. - justification: | - Callers need a safe way to obtain a full path for a file that may be nested - arbitrarily deep within the temporary directory. Auto-creating intermediate - subdirectories removes boilerplate from every caller. - tests: - - TemporaryDirectory_GetFilePath_SimpleFile_ReturnsPathUnderDirectory - - TemporaryDirectory_GetFilePath_NestedPath_CreatesIntermediateDirectories + - id: NuGetCache-TemporaryDirectory-FilePathResolution + title: The TemporaryDirectory class shall resolve relative paths to absolute paths within the temporary directory, + creating any required intermediate subdirectories. + justification: | + Callers need a safe way to obtain a full path for a file that may be nested + arbitrarily deep within the temporary directory. Auto-creating intermediate + subdirectories removes boilerplate from every caller. + tests: + - TemporaryDirectory_GetFilePath_SimpleFile_ReturnsPathUnderDirectory + - TemporaryDirectory_GetFilePath_NestedPath_CreatesIntermediateDirectories - - id: NuGetCache-TemporaryDirectory-TraversalRejection - title: The TemporaryDirectory class shall reject relative paths that escape the temporary directory boundary with - an ArgumentException. - justification: | - GetFilePath delegates to PathHelpers.SafePathCombine, which enforces the - boundary. Surfacing ArgumentException ensures callers receive a clear diagnostic - if an invalid path is supplied. - tests: - - TemporaryDirectory_GetFilePath_TraversalAttempt_ThrowsArgumentException + - id: NuGetCache-TemporaryDirectory-TraversalRejection + title: The TemporaryDirectory class shall reject relative paths that escape the temporary directory boundary + with an ArgumentException. + justification: | + GetFilePath delegates to PathHelpers.SafePathCombine, which enforces the + boundary. Surfacing ArgumentException ensures callers receive a clear diagnostic + if an invalid path is supplied. + tests: + - TemporaryDirectory_GetFilePath_TraversalAttempt_ThrowsArgumentException - - id: NuGetCache-TemporaryDirectory-Disposal - title: The TemporaryDirectory class shall delete the temporary directory and all its contents on disposal, and shall - not throw if the directory no longer exists. - justification: | - Automatic cleanup prevents accumulation of temporary artifacts after a test run - or self-test execution. Suppressing cleanup errors ensures disposal does not mask - the original outcome or break using blocks. - tests: - - TemporaryDirectory_Dispose_DeletesDirectory - - TemporaryDirectory_Dispose_AlreadyDeleted_DoesNotThrow + - id: NuGetCache-TemporaryDirectory-Disposal-DeletesContents + title: The TemporaryDirectory class shall delete the temporary directory and all its contents on disposal. + justification: | + Automatic cleanup prevents accumulation of temporary artifacts after a test run + or self-test execution. + tests: + - TemporaryDirectory_Dispose_WhenCalled_DeletesDirectory + + - id: NuGetCache-TemporaryDirectory-Disposal-IdempotentWhenMissing + title: The TemporaryDirectory class shall not throw when disposed after the temporary directory no longer + exists. + justification: | + Suppressing cleanup errors ensures disposal does not mask the original outcome or + break using blocks when the directory has already been removed (for example, by + the code under test). + tests: + - TemporaryDirectory_Dispose_AlreadyDeleted_DoesNotThrow diff --git a/docs/reqstream/ots/sysml2tools.yaml b/docs/reqstream/ots/sysml2tools.yaml new file mode 100644 index 0000000..5a74481 --- /dev/null +++ b/docs/reqstream/ots/sysml2tools.yaml @@ -0,0 +1,27 @@ +--- +# SysML2Tools OTS Software Requirements +# +# Requirements for the SysML2Tools architecture modeling tool functionality. + +sections: + - title: OTS Software Requirements + sections: + - title: SysML2Tools Requirements + requirements: + - id: NuGetCache-OTS-SysML2Tools-Lint + title: SysML2Tools shall validate the SysML2 model for syntax and reference errors. + justification: | + Linting the model in CI catches broken references and syntax errors in the + architecture model before they reach the generated documentation. + tags: [ots] + tests: + - SysML2Tools_LintSelfTest + + - id: NuGetCache-OTS-SysML2Tools-Render + title: SysML2Tools shall render the declared views to SVG diagrams. + justification: | + The rendered diagrams are embedded in the design documentation, so a failure to + render must fail the build rather than silently omit a diagram. + tags: [ots] + tests: + - SysML2Tools_RenderSvgSelfTest diff --git a/docs/reqstream/ots/test-results.yaml b/docs/reqstream/ots/test-results.yaml index 734c0bc..a2c2eff 100644 --- a/docs/reqstream/ots/test-results.yaml +++ b/docs/reqstream/ots/test-results.yaml @@ -8,15 +8,26 @@ sections: sections: - title: TestResults Requirements requirements: - - id: NuGetCache-OTS-TestResults - title: DemaConsulting.TestResults shall serialize self-validation results to TRX and JUnit XML formats. + - id: NuGetCache-OTS-TestResults-Trx + title: DemaConsulting.TestResults shall serialize self-validation results to TRX format. justification: | - DemaConsulting.TestResults provides TrxSerializer and JUnitSerializer used by - Validation.Run to write self-validation outcomes as standard test result files. - The integration tests invoke the tool with --validate --results and assert that - well-formed TRX and JUnit XML files are produced, confirming the library is - functioning correctly in the deployment environment. + DemaConsulting.TestResults provides TrxSerializer used by Validation.Run to write + self-validation outcomes as a standard MSTest-compatible TRX file. The integration + test invokes the tool with --validate --results using a .trx extension and asserts + that a well-formed TRX file is produced, confirming the library is functioning + correctly in the deployment environment. tags: [ots] tests: - NuGetCacheTool_ResultsFile_ValidateWithTrxExtension_GeneratesTrxFile + + - id: NuGetCache-OTS-TestResults-JUnit + title: DemaConsulting.TestResults shall serialize self-validation results to JUnit XML format. + justification: | + DemaConsulting.TestResults provides JUnitSerializer used by Validation.Run to write + self-validation outcomes as a standard JUnit-compatible XML file. The integration + test invokes the tool with --validate --results using a .xml extension and asserts + that a well-formed JUnit XML file is produced, confirming the library is + functioning correctly in the deployment environment. + tags: [ots] + tests: - NuGetCacheTool_ResultsFile_ValidateWithXmlExtension_GeneratesJUnitFile diff --git a/docs/sysml2/model/nuget-cache-tool.sysml b/docs/sysml2/model/nuget-cache-tool.sysml new file mode 100644 index 0000000..2b868be --- /dev/null +++ b/docs/sysml2/model/nuget-cache-tool.sysml @@ -0,0 +1,26 @@ +package NuGetCacheTool { + import OtsDependencies::*; + + doc + /* + * NuGetCacheTool is a .NET global tool that ensures NuGet packages are cached + * in the global packages folder. + */ + + part def NuGetCacheToolSystem { + doc /* The NuGetCacheTool system as a whole. */ + + comment testRef /* Test: test/DemaConsulting.NuGet.CacheTool.Tests/IntegrationTests.cs */ + comment designRef /* Design: docs/design/nuget-cache-tool.md */ + comment verificationRef /* Verification: docs/verification/nuget-cache-tool.md */ + comment reqRef /* Requirements: docs/reqstream/nuget-cache-tool.yaml */ + + part program : Program; + part cli : CliSubsystem; + part selfTest : SelfTestSubsystem; + part utilities : UtilitiesSubsystem; + + part nuGetCaching : NuGetCaching; + part testResults : TestResults; + } +} diff --git a/docs/sysml2/model/nuget-cache-tool/cli.sysml b/docs/sysml2/model/nuget-cache-tool/cli.sysml new file mode 100644 index 0000000..0cf06dc --- /dev/null +++ b/docs/sysml2/model/nuget-cache-tool/cli.sysml @@ -0,0 +1,12 @@ +package NuGetCacheTool { + part def CliSubsystem { + doc /* Command-line argument parsing and output-management subsystem. */ + + comment testRef /* Test: test/DemaConsulting.NuGet.CacheTool.Tests/Cli/CliTests.cs */ + comment designRef /* Design: docs/design/nuget-cache-tool/cli.md */ + comment verificationRef /* Verification: docs/verification/nuget-cache-tool/cli.md */ + comment reqRef /* Requirements: docs/reqstream/nuget-cache-tool/cli.yaml */ + + part context : Context; + } +} diff --git a/docs/sysml2/model/nuget-cache-tool/cli/context.sysml b/docs/sysml2/model/nuget-cache-tool/cli/context.sysml new file mode 100644 index 0000000..df6b334 --- /dev/null +++ b/docs/sysml2/model/nuget-cache-tool/cli/context.sysml @@ -0,0 +1,11 @@ +package NuGetCacheTool { + part def Context { + doc /* Command-line argument parser and centralized output-management owner. */ + + comment sourceRef /* Source: src/DemaConsulting.NuGet.CacheTool/Cli/Context.cs */ + comment testRef /* Test: test/DemaConsulting.NuGet.CacheTool.Tests/Cli/ContextTests.cs */ + comment designRef /* Design: docs/design/nuget-cache-tool/cli/context.md */ + comment verificationRef /* Verification: docs/verification/nuget-cache-tool/cli/context.md */ + comment reqRef /* Requirements: docs/reqstream/nuget-cache-tool/cli/context.yaml */ + } +} diff --git a/docs/sysml2/model/nuget-cache-tool/program.sysml b/docs/sysml2/model/nuget-cache-tool/program.sysml new file mode 100644 index 0000000..bfe4837 --- /dev/null +++ b/docs/sysml2/model/nuget-cache-tool/program.sysml @@ -0,0 +1,11 @@ +package NuGetCacheTool { + part def Program { + doc /* Entry point and application orchestrator. */ + + comment sourceRef /* Source: src/DemaConsulting.NuGet.CacheTool/Program.cs */ + comment testRef /* Test: test/DemaConsulting.NuGet.CacheTool.Tests/ProgramTests.cs */ + comment designRef /* Design: docs/design/nuget-cache-tool/program.md */ + comment verificationRef /* Verification: docs/verification/nuget-cache-tool/program.md */ + comment reqRef /* Requirements: docs/reqstream/nuget-cache-tool/program.yaml */ + } +} diff --git a/docs/sysml2/model/nuget-cache-tool/self-test.sysml b/docs/sysml2/model/nuget-cache-tool/self-test.sysml new file mode 100644 index 0000000..83b2ac3 --- /dev/null +++ b/docs/sysml2/model/nuget-cache-tool/self-test.sysml @@ -0,0 +1,12 @@ +package NuGetCacheTool { + part def SelfTestSubsystem { + doc /* Self-validation test execution subsystem, exercised via --validate. */ + + comment testRef /* Test: test/DemaConsulting.NuGet.CacheTool.Tests/SelfTest/SelfTestTests.cs */ + comment designRef /* Design: docs/design/nuget-cache-tool/self-test.md */ + comment verificationRef /* Verification: docs/verification/nuget-cache-tool/self-test.md */ + comment reqRef /* Requirements: docs/reqstream/nuget-cache-tool/self-test.yaml */ + + part validation : Validation; + } +} diff --git a/docs/sysml2/model/nuget-cache-tool/self-test/validation.sysml b/docs/sysml2/model/nuget-cache-tool/self-test/validation.sysml new file mode 100644 index 0000000..71cde2d --- /dev/null +++ b/docs/sysml2/model/nuget-cache-tool/self-test/validation.sysml @@ -0,0 +1,11 @@ +package NuGetCacheTool { + part def Validation { + doc /* Self-validation test runner exercising the full application stack. */ + + comment sourceRef /* Source: src/DemaConsulting.NuGet.CacheTool/SelfTest/Validation.cs */ + comment testRef /* Test: test/DemaConsulting.NuGet.CacheTool.Tests/SelfTest/ValidationTests.cs */ + comment designRef /* Design: docs/design/nuget-cache-tool/self-test/validation.md */ + comment verificationRef /* Verification: docs/verification/nuget-cache-tool/self-test/validation.md */ + comment reqRef /* Requirements: docs/reqstream/nuget-cache-tool/self-test/validation.yaml */ + } +} diff --git a/docs/sysml2/model/nuget-cache-tool/utilities.sysml b/docs/sysml2/model/nuget-cache-tool/utilities.sysml new file mode 100644 index 0000000..349b9d2 --- /dev/null +++ b/docs/sysml2/model/nuget-cache-tool/utilities.sysml @@ -0,0 +1,13 @@ +package NuGetCacheTool { + part def UtilitiesSubsystem { + doc /* Shared utility helpers: safe path combination and temporary directory lifecycle. */ + + comment testRef /* Test: test/DemaConsulting.NuGet.CacheTool.Tests/Utilities/UtilitiesTests.cs */ + comment designRef /* Design: docs/design/nuget-cache-tool/utilities.md */ + comment verificationRef /* Verification: docs/verification/nuget-cache-tool/utilities.md */ + comment reqRef /* Requirements: docs/reqstream/nuget-cache-tool/utilities.yaml */ + + part pathHelpers : PathHelpers; + part temporaryDirectory : TemporaryDirectory; + } +} diff --git a/docs/sysml2/model/nuget-cache-tool/utilities/path-helpers.sysml b/docs/sysml2/model/nuget-cache-tool/utilities/path-helpers.sysml new file mode 100644 index 0000000..0849658 --- /dev/null +++ b/docs/sysml2/model/nuget-cache-tool/utilities/path-helpers.sysml @@ -0,0 +1,11 @@ +package NuGetCacheTool { + part def PathHelpers { + doc /* Safe path-combination utility that prevents path-traversal attacks. */ + + comment sourceRef /* Source: src/DemaConsulting.NuGet.CacheTool/Utilities/PathHelpers.cs */ + comment testRef /* Test: test/DemaConsulting.NuGet.CacheTool.Tests/Utilities/PathHelpersTests.cs */ + comment designRef /* Design: docs/design/nuget-cache-tool/utilities/path-helpers.md */ + comment verificationRef /* Verification: docs/verification/nuget-cache-tool/utilities/path-helpers.md */ + comment reqRef /* Requirements: docs/reqstream/nuget-cache-tool/utilities/path-helpers.yaml */ + } +} diff --git a/docs/sysml2/model/nuget-cache-tool/utilities/temporary-directory.sysml b/docs/sysml2/model/nuget-cache-tool/utilities/temporary-directory.sysml new file mode 100644 index 0000000..0ef2069 --- /dev/null +++ b/docs/sysml2/model/nuget-cache-tool/utilities/temporary-directory.sysml @@ -0,0 +1,11 @@ +package NuGetCacheTool { + part def TemporaryDirectory { + doc /* Disposable temporary directory utility used by self-test and test infrastructure. */ + + comment sourceRef /* Source: src/DemaConsulting.NuGet.CacheTool/Utilities/TemporaryDirectory.cs */ + comment testRef /* Test: test/DemaConsulting.NuGet.CacheTool.Tests/Utilities/TemporaryDirectoryTests.cs */ + comment designRef /* Design: docs/design/nuget-cache-tool/utilities/temporary-directory.md */ + comment verificationRef /* Verification: docs/verification/nuget-cache-tool/utilities/temporary-directory.md */ + comment reqRef /* Requirements: docs/reqstream/nuget-cache-tool/utilities/temporary-directory.yaml */ + } +} diff --git a/docs/sysml2/model/ots.sysml b/docs/sysml2/model/ots.sysml new file mode 100644 index 0000000..0e5c191 --- /dev/null +++ b/docs/sysml2/model/ots.sysml @@ -0,0 +1,30 @@ +package OtsDependencies { + doc + /* + * Off-the-shelf (OTS) dependencies used by NuGetCacheTool. + * + * This package models only OTS items that are runtime dependencies of the shipped + * NuGetCacheTool binary. Build-time/pipeline-only OTS tooling (e.g. BuildMark, FileAssert, + * Pandoc, ReqStream, ReviewMark, SarifMark, SonarMark, SysML2Tools, VersionMark, WeasyPrint, + * xUnit) is intentionally NOT modeled here as a structural part, because this model + * represents the shipped system's runtime composition, not its build tooling. Those items + * remain fully documented via their own docs/reqstream, docs/design, and + * docs/verification OTS artifacts and OTS-{Name} review-sets in .reviewmark.yaml. + */ + + part def NuGetCaching { + doc /* OTS: DemaConsulting.NuGet.Caching. */ + + comment designRef /* Design: docs/design/ots/nuget-caching.md */ + comment verificationRef /* Verification: docs/verification/ots/nuget-caching.md */ + comment reqRef /* Requirements: docs/reqstream/ots/nuget-caching.yaml */ + } + + part def TestResults { + doc /* OTS: DemaConsulting.TestResults. */ + + comment designRef /* Design: docs/design/ots/test-results.md */ + comment verificationRef /* Verification: docs/verification/ots/test-results.md */ + comment reqRef /* Requirements: docs/reqstream/ots/test-results.yaml */ + } +} diff --git a/docs/sysml2/views/design-views.sysml b/docs/sysml2/views/design-views.sysml new file mode 100644 index 0000000..d0ccbc7 --- /dev/null +++ b/docs/sysml2/views/design-views.sysml @@ -0,0 +1,26 @@ +// Views rendered into docs/design/generated/ for the Design document. +// Reopens the NuGetCacheTool package so each 'expose' statement can reference +// its target part def directly. Per SysML2Tools syntax, 'expose' is only valid +// inside a named 'view' usage (not a 'view def' definition), and it - not +// 'render' - is what scopes the rendered diagram content. +package NuGetCacheTool { + view SoftwareStructureView { + expose NuGetCacheTool; + } + + view NuGetCacheToolView { + expose NuGetCacheToolSystem; + } + + view CliView { + expose CliSubsystem; + } + + view SelfTestView { + expose SelfTestSubsystem; + } + + view UtilitiesView { + expose UtilitiesSubsystem; + } +} diff --git a/docs/verification/definition.yaml b/docs/verification/definition.yaml index df007dd..a47e024 100644 --- a/docs/verification/definition.yaml +++ b/docs/verification/definition.yaml @@ -23,14 +23,17 @@ input-files: - docs/verification/ots.md - docs/verification/ots/buildmark.md - docs/verification/ots/fileassert.md + - docs/verification/ots/nuget-caching.md - docs/verification/ots/pandoc.md - docs/verification/ots/reqstream.md - docs/verification/ots/reviewmark.md - docs/verification/ots/sarifmark.md - docs/verification/ots/sonarmark.md + - docs/verification/ots/test-results.md - docs/verification/ots/versionmark.md - docs/verification/ots/weasyprint.md - docs/verification/ots/xunit.md + - docs/verification/ots/sysml2tools.md template: template.html table-of-contents: true number-sections: true diff --git a/docs/verification/introduction.md b/docs/verification/introduction.md index 6059136..d200083 100644 --- a/docs/verification/introduction.md +++ b/docs/verification/introduction.md @@ -26,51 +26,31 @@ This document covers the verification design for the same software items describ - **PathHelpers** — safe path combination utilities - **Program** — entry point and execution orchestrator +The following OTS items are covered, matching the *NuGet Cache Tool Software Design Document*: + +- **DemaConsulting.NuGet.Caching** — integration and usage verification. +- **DemaConsulting.TestResults** — integration and usage verification. +- **SysML2Tools** — integration and usage verification. + +`DemaConsulting.NuGet.Caching` and `DemaConsulting.TestResults` are runtime OTS packages that +this project's own source code integrates with directly, so each has requirement-linked, +unit-level verification design here. `SysML2Tools` is invoked from `lint.ps1` and +`.github/workflows/build.yaml` — build/pipeline-integrated rather than called by the shipped +tool's runtime code — but it still has its own verification design here because it generates +the SysML2 model and diagrams referenced by the design documentation. The remaining +build-pipeline-only OTS tooling (BuildMark, FileAssert, Pandoc, ReqStream, ReviewMark, +SarifMark, SonarMark, VersionMark, WeasyPrint, xUnit) is verified separately at a lighter +evidence bar — pipeline-integration evidence rather than per-unit test scenarios — documented +in *OTS Verification* (`docs/verification/ots.md`) rather than in this per-requirement +verification design. + The following topics are out of scope: +- The `DemaConsulting.NuGet.CacheTool.Tests` test project itself (its source is the verification + mechanism, not a verified software item) - Test infrastructure (xUnit framework, test helpers, Runner utility) -- Build pipeline and CI/CD configuration - -The following OTS items are also covered: - -- **BuildMark** — build-notes documentation tool -- **FileAssert** — document assertion tool -- **Pandoc** — Markdown-to-HTML conversion tool -- **ReqStream** — requirements traceability tool -- **ReviewMark** — file review enforcement tool -- **SarifMark** — SARIF report conversion tool -- **SonarMark** — SonarCloud quality report tool -- **VersionMark** — tool-version documentation tool -- **WeasyPrint** — HTML-to-PDF conversion tool -- **xUnit** — unit-testing framework - -## Software Structure - -The following tree shows the software items covered by this document: - -```text -NuGetCacheTool (System) -├── CLI (Subsystem) -│ └── Context (Unit) -├── SelfTest (Subsystem) -│ └── Validation (Unit) -├── Utilities (Subsystem) -│ ├── TemporaryDirectory (Unit) -│ └── PathHelpers (Unit) -└── Program (Unit) - -OTS Items -├── BuildMark -├── FileAssert -├── Pandoc -├── ReqStream -├── ReviewMark -├── SarifMark -├── SonarMark -├── VersionMark -├── WeasyPrint -└── xUnit -``` +- Build pipeline and CI/CD configuration itself (as distinct from the OTS tooling it invokes, + which is covered by *OTS Verification* as described above) ## Companion Artifact Structure @@ -84,8 +64,10 @@ Each local software item has corresponding artifacts in parallel directory trees OTS items have integration/usage verification documentation parallel to system folders: -- Requirements: `docs/reqstream/ots/nuget-caching.yaml`, `docs/reqstream/ots/test-results.yaml` -- Verification: `docs/verification/ots/nuget-caching.md`, `docs/verification/ots/test-results.md` +- Requirements: `docs/reqstream/ots/nuget-caching.yaml`, `docs/reqstream/ots/test-results.yaml`, + `docs/reqstream/ots/sysml2tools.yaml` +- Verification: `docs/verification/ots/nuget-caching.md`, `docs/verification/ots/test-results.md`, + `docs/verification/ots/sysml2tools.md` Review-sets: defined in `.reviewmark.yaml` diff --git a/docs/verification/nuget-cache-tool.md b/docs/verification/nuget-cache-tool.md index 701b428..f8df8a9 100644 --- a/docs/verification/nuget-cache-tool.md +++ b/docs/verification/nuget-cache-tool.md @@ -4,7 +4,7 @@ This document describes the system-level verification design for the NuGet Cache defines the overall verification strategy, test environments, interface simulation approach, and end-to-end integration test scenarios that together demonstrate the system meets its requirements. -## Verification Strategy +## Verification Approach System-level verification uses end-to-end integration tests that invoke the tool as a real process via the `Runner.Run` helper in `IntegrationTests.cs`. Each test exercises the full stack — argument @@ -16,9 +16,18 @@ observable outputs only. **Note**: `Runner.Run` merges stdout and stderr into a single combined output string. Per-stream assertions (e.g., "standard error is empty") are therefore not possible at the integration test -level; all assertions are made against the combined output. - -## Test Environments +level; all assertions are made against the combined output. Direct proof that error messages are +written specifically to stderr is instead carried by the `NuGetCache-Cli-ErrorOutput` and +`NuGetCache-Program-ErrorOutput` requirements — see *CLI Subsystem Verification* and *Program +Verification* — which capture stdout and stderr separately in their unit-level tests. Likewise, +the specific interaction between `--silent` and `--log` is proven precisely by +`NuGetCache-Cli-SilentLogInteraction` (see *CLI Subsystem Verification*), and the already-cached +idempotency behavior is proven precisely by `NuGetCache-Program-CachePackages` (see *Program +Verification*) together with the underlying `DemaConsulting.NuGet.Caching` OTS behavior (see +*DemaConsulting.NuGet.Caching Verification*); the system-level scenarios below confirm the +end-to-end behavior but do not, on their own, isolate these specific edge cases. + +## Test Environment Integration tests are executed across the following environments to satisfy multi-runtime and multi-platform requirements: @@ -45,8 +54,8 @@ The system-level integration test suite passes when all of the following conditi - All integration test scenarios defined in `IntegrationTests.cs` pass on every supported runtime and platform combination (.NET 8, 9, and 10 on Windows, Linux, and macOS). - No test scenario produces an unexpected exit code or output pattern. -- Every system-level requirement listed in the Requirements Coverage section is covered by at least - one passing scenario. +- Every system-level requirement is covered by at least one passing scenario, per the ReqStream + trace matrix. ## External Interface Simulation @@ -63,7 +72,7 @@ implementations: - **Path construction** — The `IntegrationTests` constructor uses `PathHelpers.SafePathCombine` (Utilities subsystem) to locate the tool DLL at a path derived from `AppContext.BaseDirectory`. -## Integration Test Scenarios +## Test Scenarios The following integration test scenarios are defined in `IntegrationTests.cs`. @@ -140,45 +149,3 @@ passed. `/nonexistent_dir_xyz_abc/invalid.log`. **Expected**: Exit code non-zero; combined output contains "Error". - -## Requirements Coverage - -**Note on `NuGetCache-Sys-PathSafety`**: This security requirement is verified at unit level -(see `docs/verification/nuget-cache-tool/utilities/path-helpers.md`). At the system level, -`NuGetCacheTool_LogFile_InvalidFilenameProvided_ReturnsError` exercises error handling for -invalid paths, but dedicated path-traversal prevention is verified in the PathHelpers unit tests. - -The following list maps each system-level requirement to the integration test scenarios that -verify it. - -- **`NuGetCache-Sys-Integration`**: NuGetCacheTool_VersionDisplay_VersionFlagProvided_OutputsVersion, - NuGetCacheTool_HelpDisplay_HelpFlagProvided_OutputsUsageInformation, NuGetCacheTool_PackageCaching_ValidPackageProvided_OutputsPath, - NuGetCacheTool_SelfValidation_ValidateFlagProvided_RunsValidation, NuGetCacheTool_SilentMode_SilentFlagProvided_SuppressesOutput, - NuGetCacheTool_LogFile_LogFlagProvided_WritesOutputToFile, NuGetCacheTool_ErrorHandling_UnknownArgumentProvided_ReturnsError, - NuGetCacheTool_PackageCaching_NonexistentPackageProvided_ReturnsError -- **`NuGetCache-Sys-ValidateResults`**: NuGetCacheTool_ResultsFile_ValidateWithTrxExtension_GeneratesTrxFile, - NuGetCacheTool_ResultsFile_ValidateWithXmlExtension_GeneratesJUnitFile -- **`NuGetCache-Sys-SilentMode`**: NuGetCacheTool_SilentMode_SilentFlagProvided_SuppressesOutput -- **`NuGetCache-Sys-LogFile`**: NuGetCacheTool_LogFile_LogFlagProvided_WritesOutputToFile, - NuGetCacheTool_LogFile_InvalidFilenameProvided_ReturnsError -- **`NuGetCache-Sys-Banner`**: NuGetCacheTool_SelfValidation_ValidateFlagProvided_RunsValidation, - NuGetCacheTool_LogFile_LogFlagProvided_WritesOutputToFile -- **`NuGetCache-Sys-InvalidArguments`**: NuGetCacheTool_ErrorHandling_UnknownArgumentProvided_ReturnsError -- **`NuGetCache-Sys-ExitCode`**: NuGetCacheTool_PackageCaching_NonexistentPackageProvided_ReturnsError, - NuGetCacheTool_ErrorHandling_UnknownArgumentProvided_ReturnsError -- **`NuGetCache-Sys-StderrOutput`**: NuGetCacheTool_PackageCaching_NonexistentPackageProvided_ReturnsError, - NuGetCacheTool_ErrorHandling_UnknownArgumentProvided_ReturnsError. - **Accepted limitation**: `Runner.Run` merges stdout and stderr into a single combined - output string, so these tests can only confirm that "Error" appears somewhere in the - combined output rather than confirming the message was sent specifically to stderr. - Stderr routing is verified at the unit level by `Context_WriteError_NotSilent_WritesToConsole` - in `docs/verification/nuget-cache-tool/cli/context.md`. -- **`NuGetCache-Sys-SilentLogInteraction`**: NuGetCacheTool_LogFile_LogFlagProvided_WritesOutputToFile - -- **`NuGetCache-Sys-PathSafety`**: Verified at unit level in - `docs/verification/nuget-cache-tool/utilities/path-helpers.md`. - The `PathHelpers.SafePathCombine` unit tests (PathHelpers_SafePathCombine_PathTraversalWithDoubleDots_ThrowsArgumentException, - PathHelpers_SafePathCombine_WindowsAbsolutePath_ThrowsArgumentException, - PathHelpers_SafePathCombine_UnixAbsolutePath_ThrowsArgumentException) directly exercise - the path-traversal prevention mechanism. No integration-level scenario is required - because path safety is a pure unit-level concern that does not depend on external interfaces. diff --git a/docs/verification/nuget-cache-tool/cli.md b/docs/verification/nuget-cache-tool/cli.md index 70396ad..2593bb2 100644 --- a/docs/verification/nuget-cache-tool/cli.md +++ b/docs/verification/nuget-cache-tool/cli.md @@ -1,7 +1,8 @@ ## CLI Subsystem Verification This document describes the subsystem-level verification design for the `Cli` subsystem. It -defines the overall verification strategy and requirement coverage for the CLI subsystem. +defines the overall verification strategy and test scenarios for the CLI subsystem. Requirement +traceability is tracked in the ReqStream trace matrix, not in this document. ### Verification Approach @@ -30,8 +31,8 @@ The CLI subsystem test suite passes when all of the following conditions are met - All test scenarios defined in `CliTests.cs` pass. - No scenario produces an unexpected context state, exit code, or console output. -- Every CLI subsystem requirement listed in the Requirements Coverage section is covered by at - least one passing scenario. +- Every CLI subsystem requirement is covered by at least one passing scenario, as tracked in the + ReqStream trace matrix. ### Test Scenarios @@ -41,40 +42,30 @@ The CLI subsystem test suite passes when all of the following conditions are met **Expected**: `context.Version` is true; `context.Help` is false; exit code is 0. -**Requirement coverage**: `NuGetCache-Cli-VersionFlag`. - #### Cli_ShortVersionFlag_SetsVersionOnContext **Scenario**: Context is created from `["-v"]`. **Expected**: `context.Version` is true; exit code is 0. -**Requirement coverage**: `NuGetCache-Cli-VersionFlag`. - #### Cli_HelpFlag_SetsHelpOnContext **Scenario**: Context is created from `["--help"]`. **Expected**: `context.Help` is true; `context.Version` is false; exit code is 0. -**Requirement coverage**: `NuGetCache-Cli-HelpFlag`. - #### Cli_ShortHelpFlagH_SetsHelpOnContext **Scenario**: Context is created from `["-h"]`. **Expected**: `context.Help` is true; exit code is 0. -**Requirement coverage**: `NuGetCache-Cli-HelpFlag`. - #### Cli_ShortHelpFlagQuestionMark_SetsHelpOnContext **Scenario**: Context is created from `["-?"]`. **Expected**: `context.Help` is true; exit code is 0. -**Requirement coverage**: `NuGetCache-Cli-HelpFlag`. - #### Cli_SilentFlag_SuppressesAllOutput **Scenario**: Context is created from `["--silent"]`; `context.WriteLine` and @@ -82,102 +73,63 @@ The CLI subsystem test suite passes when all of the following conditions are met **Expected**: Neither message appears on stdout or stderr. -**Requirement coverage**: `NuGetCache-Cli-SilentFlag`. - #### Cli_PackageArgument_AddedToPackagesList **Scenario**: Context is created from `["Package.One:1.0.0", "Package.Two:2.3.4"]`. **Expected**: `context.Packages` contains exactly 2 entries matching both arguments. -**Requirement coverage**: `NuGetCache-Cli-CachePackages`. - #### Cli_ErrorOutput_SetsNonZeroExitCode **Scenario**: Context is created from `[]`; `context.WriteError` is called. **Expected**: Exit code is non-zero. -**Requirement coverage**: `NuGetCache-Cli-ErrorOutput`. - #### Cli_ErrorOutput_WritesMessageToConsole **Scenario**: Context is created from `[]`; `context.WriteError` is called with a message. **Expected**: The error message appears in stderr. -**Requirement coverage**: `NuGetCache-Cli-ErrorOutput`. - #### Cli_UnknownArgument_ThrowsArgumentException **Scenario**: Context is created from `["--unknown-flag"]`. **Expected**: `ArgumentException` is thrown with a message containing "Unsupported argument". -**Requirement coverage**: `NuGetCache-Cli-InvalidArguments`. - #### Cli_LogFlag_WritesToLogFile **Scenario**: Context is created from `["--log", logFile]`; `context.WriteLine` is called. **Expected**: The log file is created and contains the written message. -**Requirement coverage**: `NuGetCache-Cli-LogFlag`. - #### Cli_ValidateFlag_SetsValidateOnContext **Scenario**: Context is created from `["--validate"]`. **Expected**: `context.Validate` is true; exit code is 0. -**Requirement coverage**: `NuGetCache-Cli-ValidateFlag`. - #### Cli_ResultsFlag_SetsResultsFileOnContext **Scenario**: Context is created from `["--results", "results.trx"]`. **Expected**: `context.ResultsFile` equals "results.trx"; exit code is 0. -**Requirement coverage**: `NuGetCache-Cli-ResultsFlag`. - #### Cli_LogFlagWithoutValue_ThrowsArgumentException **Scenario**: Context is created from `["--log"]` (missing value). **Expected**: `ArgumentException` is thrown. -**Requirement coverage**: `NuGetCache-Cli-InvalidArguments`. - #### Cli_ResultsFlagWithoutValue_ThrowsArgumentException **Scenario**: Context is created from `["--results"]` (missing value). **Expected**: `ArgumentException` is thrown. -**Requirement coverage**: `NuGetCache-Cli-InvalidArguments`. - #### Cli_SilentAndLog_WritesToLogFileOnly **Scenario**: Context is created from `["--silent", "--log", logFile]`; `context.WriteLine` is called. **Expected**: Message does not appear on stdout; log file is created and contains the message. - -**Requirement coverage**: `NuGetCache-Cli-SilentLogInteraction`. - -### Requirements Coverage - -- **`NuGetCache-Cli-VersionFlag`**: Cli_VersionFlag_SetsVersionOnContext, - Cli_ShortVersionFlag_SetsVersionOnContext -- **`NuGetCache-Cli-HelpFlag`**: Cli_HelpFlag_SetsHelpOnContext, Cli_ShortHelpFlagH_SetsHelpOnContext, - Cli_ShortHelpFlagQuestionMark_SetsHelpOnContext -- **`NuGetCache-Cli-SilentFlag`**: Cli_SilentFlag_SuppressesAllOutput -- **`NuGetCache-Cli-CachePackages`**: Cli_PackageArgument_AddedToPackagesList -- **`NuGetCache-Cli-ErrorOutput`**: Cli_ErrorOutput_SetsNonZeroExitCode, - Cli_ErrorOutput_WritesMessageToConsole -- **`NuGetCache-Cli-LogFlag`**: Cli_LogFlag_WritesToLogFile -- **`NuGetCache-Cli-ValidateFlag`**: Cli_ValidateFlag_SetsValidateOnContext -- **`NuGetCache-Cli-ResultsFlag`**: Cli_ResultsFlag_SetsResultsFileOnContext -- **`NuGetCache-Cli-InvalidArguments`**: Cli_UnknownArgument_ThrowsArgumentException, - Cli_LogFlagWithoutValue_ThrowsArgumentException, Cli_ResultsFlagWithoutValue_ThrowsArgumentException -- **`NuGetCache-Cli-SilentLogInteraction`**: Cli_SilentAndLog_WritesToLogFileOnly diff --git a/docs/verification/nuget-cache-tool/cli/context.md b/docs/verification/nuget-cache-tool/cli/context.md index 64df5db..0a986b3 100644 --- a/docs/verification/nuget-cache-tool/cli/context.md +++ b/docs/verification/nuget-cache-tool/cli/context.md @@ -27,8 +27,8 @@ The Context unit test suite passes when all of the following conditions are met: - All test scenarios defined in `ContextTests.cs` pass. - Console and log-file output assertions match expected values for every flag combination. -- Every Context unit requirement listed in the Requirements Coverage section is covered by at - least one passing scenario. +- Every Context unit requirement is covered by at least one passing scenario, per the ReqStream + trace matrix. #### Test Scenarios @@ -166,7 +166,7 @@ The Context unit test suite passes when all of the following conditions are met: **Expected**: `ArgumentException` is thrown with "Unsupported argument" in the message. -**Requirement coverage**: `NuGetCache-Context-InvalidArguments`. +**Requirement coverage**: `NuGetCache-Context-InvalidArguments-UnknownFlag`. ##### Context_Create_LogFlag_WithoutValue_ThrowsArgumentException @@ -174,7 +174,7 @@ The Context unit test suite passes when all of the following conditions are met: **Expected**: `ArgumentException` is thrown with "--log" in the message. -**Requirement coverage**: `NuGetCache-Context-InvalidArguments`. +**Requirement coverage**: `NuGetCache-Context-InvalidArguments-MissingLogValue`. ##### Context_Create_ResultsFlag_WithoutValue_ThrowsArgumentException @@ -182,7 +182,7 @@ The Context unit test suite passes when all of the following conditions are met: **Expected**: `ArgumentException` is thrown with "--results" in the message. -**Requirement coverage**: `NuGetCache-Context-InvalidArguments`. +**Requirement coverage**: `NuGetCache-Context-InvalidArguments-MissingResultsValue`. ##### Context_Create_WithoutColonInPackage_ThrowsArgumentException @@ -190,7 +190,7 @@ The Context unit test suite passes when all of the following conditions are met: **Expected**: `ArgumentException` is thrown. -**Requirement coverage**: `NuGetCache-Context-InvalidArguments`. +**Requirement coverage**: `NuGetCache-Context-InvalidArguments-MalformedPackage`. ##### Context_WriteError_Silent_DoesNotWriteToConsole @@ -199,23 +199,3 @@ The Context unit test suite passes when all of the following conditions are met: **Expected**: The message does not appear in stderr. **Requirement coverage**: `NuGetCache-Context-SilentOutput`. - -#### Requirements Coverage - -- **`NuGetCache-Context-ArgumentParsing`**: Context_Create_NoArguments_ReturnsDefaultContext, - Context_Create_VersionFlag_SetsVersionTrue, Context_Create_ShortVersionFlag_SetsVersionTrue, - Context_Create_HelpFlag_SetsHelpTrue, Context_Create_ShortHelpFlag_H_SetsHelpTrue, - Context_Create_ShortHelpFlag_Question_SetsHelpTrue, Context_Create_SilentFlag_SetsSilentTrue, - Context_Create_ValidateFlag_SetsValidateTrue, Context_Create_ResultsFlag_SetsResultsFile, - Context_Create_LogFlag_OpensLogFile, Context_Create_PackageArgument_AddsToPackagesList -- **`NuGetCache-Context-SilentOutput`**: Context_Create_SilentFlag_SetsSilentTrue, - Context_WriteLine_NotSilent_WritesToConsole, Context_WriteLine_Silent_DoesNotWriteToConsole, - Context_WriteError_Silent_DoesNotWriteToConsole -- **`NuGetCache-Context-LogFile`**: Context_Create_LogFlag_OpensLogFile, - Context_WriteError_WritesToLogFile -- **`NuGetCache-Context-ErrorTracking`**: Context_WriteError_SetsErrorExitCode, - Context_WriteError_NotSilent_WritesToConsole -- **`NuGetCache-Context-InvalidArguments`**: Context_Create_UnknownArgument_ThrowsArgumentException, - Context_Create_LogFlag_WithoutValue_ThrowsArgumentException, - Context_Create_ResultsFlag_WithoutValue_ThrowsArgumentException, - Context_Create_WithoutColonInPackage_ThrowsArgumentException diff --git a/docs/verification/nuget-cache-tool/program.md b/docs/verification/nuget-cache-tool/program.md index 6bd0612..ce83f2b 100644 --- a/docs/verification/nuget-cache-tool/program.md +++ b/docs/verification/nuget-cache-tool/program.md @@ -32,8 +32,8 @@ The Program unit test suite passes when all of the following conditions are met: - All test scenarios defined in `ProgramTests.cs` pass. - Console output assertions match the expected content for every invocation pattern. -- Every Program unit requirement listed in the Requirements Coverage section is covered by at - least one passing scenario. +- Every Program unit requirement is covered by at least one passing scenario, per the ReqStream + trace matrix. ### Test Scenarios @@ -96,13 +96,3 @@ is 0. **Expected**: Exit code 1; error output contains "Error". **Requirement coverage**: `NuGetCache-Program-ErrorOutput`. - -### Requirements Coverage - -- **`NuGetCache-Program-VersionDisplay`**: Program_Run_WithVersionFlag_DisplaysVersionOnly, - Program_Version_ReturnsNonEmptyString -- **`NuGetCache-Program-HelpDisplay`**: Program_Run_WithHelpFlag_DisplaysUsageInformation -- **`NuGetCache-Program-CachePackages`**: Program_Run_WithPackageArgument_CachesPackage -- **`NuGetCache-Program-Banner`**: Program_Run_NoArguments_DisplaysDefaultBehavior -- **`NuGetCache-Program-ErrorOutput`**: Program_Run_WithValidateAndUnsupportedResultsFormat_SetsErrorExitCode -- **`NuGetCache-Program-SelfValidation`**: Program_Run_WithValidateFlag_RunsValidation diff --git a/docs/verification/nuget-cache-tool/self-test.md b/docs/verification/nuget-cache-tool/self-test.md index 0234dbd..0fcdc16 100644 --- a/docs/verification/nuget-cache-tool/self-test.md +++ b/docs/verification/nuget-cache-tool/self-test.md @@ -29,8 +29,8 @@ The SelfTest subsystem test suite passes when all of the following conditions ar - All test scenarios defined in `SelfTestTests.cs` pass. - Results files generated during testing are created at the expected path with valid content. -- Every SelfTest subsystem requirement listed in the Requirements Coverage section is covered by - at least one passing scenario. +- Every SelfTest subsystem requirement is covered by at least one passing scenario, per the + ReqStream trace matrix. ### Test Scenarios @@ -67,9 +67,12 @@ The SelfTest subsystem test suite passes when all of the following conditions ar **Requirement coverage**: `NuGetCache-SelfTest-ResultsFile`. -### Requirements Coverage +#### SelfTest_ResultsFile_UnsupportedExtension_ReportsErrorAndSkipsFile -- **`NuGetCache-SelfTest-Validation`**: SelfTest_Validation_ExecutesSelfValidationTests, - SelfTest_Validation_ReportsPassFail -- **`NuGetCache-SelfTest-ResultsFile`**: SelfTest_ResultsFile_GeneratesTrxFile, - SelfTest_ResultsFile_GeneratesJUnitFile +**Scenario**: `Validation.Run` is called with `["--validate", "--silent", "--results", resultsFile]` +where `resultsFile` has an unsupported extension (neither `.trx` nor `.xml`). + +**Expected**: Non-zero exit code (an error is reported via `context.WriteError`); no results +file is created at the requested path. + +**Requirement coverage**: `NuGetCache-SelfTest-ResultsFile`. diff --git a/docs/verification/nuget-cache-tool/self-test/validation.md b/docs/verification/nuget-cache-tool/self-test/validation.md index 27bd313..0ff685f 100644 --- a/docs/verification/nuget-cache-tool/self-test/validation.md +++ b/docs/verification/nuget-cache-tool/self-test/validation.md @@ -5,9 +5,11 @@ the test scenarios, dependency usage, and requirement coverage for `Validation.c #### Verification Approach -`Validation` is verified with unit tests defined in `ValidationTests.cs`. The tests create +`Validation` is verified with unit tests defined in `ValidationTests.cs`. Most tests create controlled `Context` instances and call `Validation.Run` directly, verifying results file -generation and error handling for unsupported formats. +generation and error handling for unsupported formats. The `ValidateCachePackagePath` helper is +additionally verified with direct unit tests exercising known-good and known-bad paths, proving +the exact-match identity check would catch a regression to substring matching. #### Dependencies @@ -21,8 +23,11 @@ No mocking is required; all collaborators execute real logic. Validation unit tests run under the standard xUnit v3 test runner within the `DemaConsulting.NuGet.CacheTool.Tests` project. Tests that generate results files create -temporary files and clean them up on completion. No external services or network connectivity -are required. +temporary files and clean them up on completion. Most scenarios require no external services or +network connectivity; the `Validation_Run_CachePackageSelfTest_PassesWithRealCachedPackagePath` +scenario invokes the tool's cache-package self-test against a real NuGet package identity, which +requires network access if that package is not already present in the local NuGet global +packages folder. #### Acceptance Criteria @@ -31,8 +36,8 @@ The Validation unit test suite passes when all of the following conditions are m - All test scenarios defined in `ValidationTests.cs` pass. - Results files are created at the expected path with syntactically valid TRX or JUnit XML content. -- Every Validation unit requirement listed in the Requirements Coverage section is covered by - at least one passing scenario. +- Every Validation unit requirement is covered by at least one passing scenario, per the + ReqStream trace matrix. #### Test Scenarios @@ -70,9 +75,72 @@ the log file only. **Requirement coverage**: `NuGetCache-Validation-SelfValidation`. -#### Requirements Coverage +##### Validation_Run_CachePackageSelfTest_PassesWithRealCachedPackagePath -- **`NuGetCache-Validation-SelfValidation`**: Validation_Run_WithSilentContext_PrintsSummary -- **`NuGetCache-Validation-ResultsFile`**: Validation_Run_TrxResultsRequested_WritesTrxFile, - Validation_Run_JUnitResultsRequested_WritesJUnitFile, - Validation_Run_UnsupportedResultsFormat_ReportsError +**Scenario**: `Validation.Run` is called with `["--validate"]` against a real, populated NuGet +global packages folder; standard output is captured. + +**Expected**: Captured output contains "Cache Package Test - PASSED"; exit code 0. + +**Requirement coverage**: `NuGetCache-Validation-SelfValidation`. + +##### Validation_ValidateCachePackagePath_ExactMatch_ReturnsNull + +**Scenario**: `Validation.ValidateCachePackagePath` is called with a path whose directory name +and parent directory name exactly match the expected version and package ID. + +**Expected**: Returns `null` (no error). + +**Requirement coverage**: `NuGetCache-Validation-SelfValidation`. + +##### Validation_ValidateCachePackagePath_VersionSuffixSubstringMatch_ReturnsError + +**Scenario**: `Validation.ValidateCachePackagePath` is called with a version directory name that +contains the expected version as a prefix followed by a suffix (e.g. `0.1.0-beta` against +expected `0.1.0`). + +**Expected**: Returns a non-null error message, proving the check is an exact match rather than a +substring match. + +**Requirement coverage**: `NuGetCache-Validation-SelfValidation`. + +##### Validation_ValidateCachePackagePath_VersionPrefixSubstringMatch_ReturnsError + +**Scenario**: `Validation.ValidateCachePackagePath` is called with a version directory name that +contains the expected version as a substring preceded by extra digits (e.g. `10.1.0` against +expected `0.1.0`). + +**Expected**: Returns a non-null error message, proving the check is an exact match rather than a +substring match. + +**Requirement coverage**: `NuGetCache-Validation-SelfValidation`. + +##### Validation_ValidateCachePackagePath_WrongPackageId_ReturnsError + +**Scenario**: `Validation.ValidateCachePackagePath` is called with a path whose parent directory +name does not match the expected package ID. + +**Expected**: Returns a non-null error message. + +**Requirement coverage**: `NuGetCache-Validation-SelfValidation`. + +##### Validation_ValidateCachePackagePath_NoParentDirectory_ReturnsError + +**Scenario**: `Validation.ValidateCachePackagePath` is called with a root path (`"/"`), for which +`Path.GetDirectoryName` returns `null` rather than a parent directory name. + +**Expected**: Returns a non-null error message, proving the null-parent-directory case is handled +as a non-match rather than throwing. + +**Requirement coverage**: `NuGetCache-Validation-SelfValidation`. + +##### Validation_ValidateCachePackagePath_BareRelativeName_ReturnsError + +**Scenario**: `Validation.ValidateCachePackagePath` is called with a bare relative directory name +with no path separator (`"0.1.0"`), for which `Path.GetDirectoryName` returns an empty string +rather than `null`. + +**Expected**: Returns a non-null error message, proving the empty-parent-directory-name case is +handled as a non-match. + +**Requirement coverage**: `NuGetCache-Validation-SelfValidation`. diff --git a/docs/verification/nuget-cache-tool/utilities.md b/docs/verification/nuget-cache-tool/utilities.md index 9f53e9b..070ab38 100644 --- a/docs/verification/nuget-cache-tool/utilities.md +++ b/docs/verification/nuget-cache-tool/utilities.md @@ -33,8 +33,8 @@ working directory. No external services or network connectivity are required. The Utilities subsystem test suite passes when all of the following conditions are met: - All test scenarios defined in `UtilitiesTests.cs` pass. -- Every Utilities subsystem requirement listed in the Requirements Coverage section is covered by - at least one passing scenario. +- Every Utilities subsystem requirement is covered by at least one passing scenario, as tracked + in the ReqStream trace matrix. - No tests may be skipped or marked as expected failures. ### Test Scenarios @@ -89,12 +89,3 @@ path between the absolute base and the absolute combined path contains no leadin and is not rooted. **Requirement coverage**: `NuGetCache-Utilities-PathSafety`. - -### Requirements Coverage - -- **`NuGetCache-Utilities-PathSafety`**: Utilities_PathTraversal_TraversalAttempt_ThrowsArgumentException, - Utilities_PathSafety_SafePathCombine_StaysWithinBase -- **`NuGetCache-Utilities-TempDirectory`**: Utilities_PathResolution_ValidRelativePath_ReturnsPathWithinDirectory, - Utilities_PathResolution_NestedRelativePath_CreatesIntermediateDirectories, - Utilities_PathTraversal_TraversalAttempt_ThrowsArgumentException, - Utilities_DirectoryLifecycle_CreateAndDispose_DirectoryCreatedThenDeleted diff --git a/docs/verification/nuget-cache-tool/utilities/path-helpers.md b/docs/verification/nuget-cache-tool/utilities/path-helpers.md index 07f44c6..002b98d 100644 --- a/docs/verification/nuget-cache-tool/utilities/path-helpers.md +++ b/docs/verification/nuget-cache-tool/utilities/path-helpers.md @@ -27,8 +27,8 @@ The PathHelpers unit test suite passes when all of the following conditions are - All test scenarios defined in `PathHelpersTests.cs` pass. - All boundary and error-path scenarios produce the expected exception type with the expected message content. -- Every PathHelpers unit requirement listed in the Requirements Coverage section is covered by - at least one passing scenario. +- Every PathHelpers unit requirement is covered by at least one passing scenario, per the + ReqStream trace matrix. #### Test Scenarios @@ -73,6 +73,17 @@ The PathHelpers unit test suite passes when all of the following conditions are **Requirement coverage**: `NuGetCache-PathHelpers-SafePathCombine`. +##### PathHelpers_SafePathCombine_RootedPathInsideBase_ThrowsArgumentException + +**Scenario**: `SafePathCombine` is called with a rooted `relativePath` that already resolves +inside `basePath` (for example, `basePath` combined with itself to form the rooted input). + +**Expected**: `ArgumentException` is thrown with "Invalid path component" in the message, proving +the rooted-path rejection happens upfront rather than only after resolving whether the result +escapes `basePath`. + +**Requirement coverage**: `NuGetCache-PathHelpers-SafePathCombine`. + ##### PathHelpers_SafePathCombine_CurrentDirectoryReference_CombinesCorrectly **Scenario**: `SafePathCombine` is called with `"./subfolder/file.txt"`. @@ -121,17 +132,3 @@ The PathHelpers unit test suite passes when all of the following conditions are **Expected**: `ArgumentNullException` is thrown. **Requirement coverage**: `NuGetCache-PathHelpers-NullArguments`. - -#### Requirements Coverage - -- **`NuGetCache-PathHelpers-SafePathCombine`**: PathHelpers_SafePathCombine_ValidPaths_CombinesCorrectly, - PathHelpers_SafePathCombine_PathTraversalWithDoubleDots_ThrowsArgumentException, - PathHelpers_SafePathCombine_DoubleDotsInMiddle_ThrowsArgumentException, - PathHelpers_SafePathCombine_UnixAbsolutePath_ThrowsArgumentException, - PathHelpers_SafePathCombine_WindowsAbsolutePath_ThrowsArgumentException, - PathHelpers_SafePathCombine_CurrentDirectoryReference_CombinesCorrectly, - PathHelpers_SafePathCombine_NestedPaths_CombinesCorrectly, - PathHelpers_SafePathCombine_EmptyRelativePath_ReturnsBasePath, - PathHelpers_SafePathCombine_DoubleDotPrefix_CombinesCorrectly -- **`NuGetCache-PathHelpers-NullArguments`**: PathHelpers_SafePathCombine_NullBase_ThrowsArgumentNullException, - PathHelpers_SafePathCombine_NullRelative_ThrowsArgumentNullException diff --git a/docs/verification/nuget-cache-tool/utilities/temporary-directory.md b/docs/verification/nuget-cache-tool/utilities/temporary-directory.md index 18fd060..2002e29 100644 --- a/docs/verification/nuget-cache-tool/utilities/temporary-directory.md +++ b/docs/verification/nuget-cache-tool/utilities/temporary-directory.md @@ -10,6 +10,12 @@ It defines test scenarios, dependency usage, and requirement coverage for exercise construction, file-path resolution, path-traversal rejection, and disposal directly against the file system under `Environment.CurrentDirectory`. +The constructor's `InvalidOperationException` wrapping of `Directory.CreateDirectory` failures +(see *TemporaryDirectory Design*) is a defensive guard, not a directly unit-tested scenario: +reliably forcing that underlying call to fail requires environment-specific, OS-level +file-system permission manipulation (for example, Windows `icacls`) that is itself flaky and +platform-fragile, so this path is verified by code inspection rather than an automated test. + #### Dependencies | Dependency | Usage in Tests | @@ -24,19 +30,19 @@ concurrent test runs sharing the same working directory. #### Acceptance Criteria -All unit tests in `TemporaryDirectoryTests.cs` pass; all requirements listed in the -Requirements Coverage section have at least one passing test scenario; no tests may be -skipped or marked as expected failures. +All unit tests in `TemporaryDirectoryTests.cs` pass; every requirement for this unit has at +least one passing test scenario, per the ReqStream trace matrix; no tests may be skipped or +marked as expected failures. #### Test Scenarios -##### TemporaryDirectory_Constructor_CreatesDirectory +##### TemporaryDirectory_Constructor_WhenCalled_CreatesDirectoryOnDisk **Scenario**: A `TemporaryDirectory` instance is constructed. **Expected**: `Directory.Exists(DirectoryPath)` returns `true`. -##### TemporaryDirectory_Constructor_CreatesUniqueDirectories +##### TemporaryDirectory_Constructor_MultipleInstances_CreatesUniqueDirectories **Scenario**: Two `TemporaryDirectory` instances are constructed in sequence. @@ -61,7 +67,7 @@ skipped or marked as expected failures. **Expected**: `ArgumentException` is thrown. -##### TemporaryDirectory_Dispose_DeletesDirectory +##### TemporaryDirectory_Dispose_WhenCalled_DeletesDirectory **Scenario**: A `TemporaryDirectory` is created, a file is written inside it, and the instance is disposed. @@ -73,17 +79,3 @@ instance is disposed. **Scenario**: The underlying directory is deleted manually before `Dispose` is called. **Expected**: No exception is thrown. - -#### Requirements Coverage - -- **`NuGetCache-TemporaryDirectory-DirectoryCreation`**: - TemporaryDirectory_Constructor_CreatesDirectory, - TemporaryDirectory_Constructor_CreatesUniqueDirectories. -- **`NuGetCache-TemporaryDirectory-FilePathResolution`**: - TemporaryDirectory_GetFilePath_SimpleFile_ReturnsPathUnderDirectory, - TemporaryDirectory_GetFilePath_NestedPath_CreatesIntermediateDirectories. -- **`NuGetCache-TemporaryDirectory-TraversalRejection`**: - TemporaryDirectory_GetFilePath_TraversalAttempt_ThrowsArgumentException. -- **`NuGetCache-TemporaryDirectory-Disposal`**: - TemporaryDirectory_Dispose_DeletesDirectory, - TemporaryDirectory_Dispose_AlreadyDeleted_DoesNotThrow. diff --git a/docs/verification/ots.md b/docs/verification/ots.md index 8dd86f8..1661526 100644 --- a/docs/verification/ots.md +++ b/docs/verification/ots.md @@ -42,13 +42,16 @@ When an OTS item is upgraded to a new version: | OTS Item | Verification Document | | -------- | --------------------- | -| BuildMark | [ots/buildmark.md](ots/buildmark.md) | -| FileAssert | [ots/fileassert.md](ots/fileassert.md) | -| Pandoc | [ots/pandoc.md](ots/pandoc.md) | -| ReqStream | [ots/reqstream.md](ots/reqstream.md) | -| ReviewMark | [ots/reviewmark.md](ots/reviewmark.md) | -| SarifMark | [ots/sarifmark.md](ots/sarifmark.md) | -| SonarMark | [ots/sonarmark.md](ots/sonarmark.md) | -| VersionMark | [ots/versionmark.md](ots/versionmark.md) | -| WeasyPrint | [ots/weasyprint.md](ots/weasyprint.md) | -| xUnit | [ots/xunit.md](ots/xunit.md) | +| BuildMark | ots/buildmark.md | +| FileAssert | ots/fileassert.md | +| DemaConsulting.NuGet.Caching | ots/nuget-caching.md | +| Pandoc | ots/pandoc.md | +| ReqStream | ots/reqstream.md | +| ReviewMark | ots/reviewmark.md | +| SarifMark | ots/sarifmark.md | +| SonarMark | ots/sonarmark.md | +| SysML2Tools | ots/sysml2tools.md | +| DemaConsulting.TestResults | ots/test-results.md | +| VersionMark | ots/versionmark.md | +| WeasyPrint | ots/weasyprint.md | +| xUnit | ots/xunit.md | diff --git a/docs/verification/ots/buildmark.md b/docs/verification/ots/buildmark.md index 1a270a8..6127fb1 100644 --- a/docs/verification/ots/buildmark.md +++ b/docs/verification/ots/buildmark.md @@ -32,7 +32,3 @@ BuildMark did not produce the required output. in the release artifacts. **Requirement coverage**: `NuGetCache-OTS-BuildMark`. - -### Requirements Coverage - -- **`NuGetCache-OTS-BuildMark`**: BuildMark_MarkdownReportGeneration diff --git a/docs/verification/ots/fileassert.md b/docs/verification/ots/fileassert.md index 855f30d..cd13363 100644 --- a/docs/verification/ots/fileassert.md +++ b/docs/verification/ots/fileassert.md @@ -42,7 +42,3 @@ asserted document content at each stage of the pipeline. **Expected**: Displays usage information without error. **Requirement coverage**: `NuGetCache-OTS-FileAssert`. - -### Requirements Coverage - -- **`NuGetCache-OTS-FileAssert`**: FileAssert_VersionDisplay, FileAssert_HelpDisplay diff --git a/docs/verification/ots/nuget-caching.md b/docs/verification/ots/nuget-caching.md new file mode 100644 index 0000000..cd52ba9 --- /dev/null +++ b/docs/verification/ots/nuget-caching.md @@ -0,0 +1,40 @@ +## DemaConsulting.NuGet.Caching Verification + +This document provides the verification evidence for the `DemaConsulting.NuGet.Caching` OTS +software item. Requirements for this OTS item are defined in the DemaConsulting.NuGet.Caching +OTS Software Requirements document. + +### Required Functionality + +`DemaConsulting.NuGet.Caching` shall cache NuGet packages to the global packages folder via +the `NuGetCache.EnsureCachedAsync` API, returning the path to the cached package. See +*DemaConsulting.NuGet.Caching Integration Design* for the integration pattern. + +### Verification Approach + +`DemaConsulting.NuGet.Caching` is verified through the NuGet Cache Tool's own integration test +suite, which invokes the tool end-to-end against the live NuGet feed, exercising +`NuGetCache.EnsureCachedAsync` for both valid and invalid package arguments. + +### Test Scenarios + +#### NuGetCacheTool_PackageCaching_ValidPackageProvided_OutputsPath + +**Scenario**: The tool is invoked with a valid `package:version` argument. + +**Expected**: `NuGetCache.EnsureCachedAsync` resolves and caches the package, and the tool +writes a non-empty package path to output. + +#### NuGetCacheTool_PackageCaching_NonexistentPackageProvided_ReturnsError + +**Scenario**: The tool is invoked with a `package:version` argument that does not exist on the +configured NuGet feed. + +**Expected**: `NuGetCache.EnsureCachedAsync` throws, the tool reports the error via +`context.WriteError`, and the process exit code is non-zero. + +### Acceptance Criteria + +N/A - Acceptance criteria are managed at the system integration level. This OTS item is +considered verified when the integration test scenarios that exercise its functionality pass +in the CI pipeline. diff --git a/docs/verification/ots/pandoc.md b/docs/verification/ots/pandoc.md index 33aa5db..190d7c4 100644 --- a/docs/verification/ots/pandoc.md +++ b/docs/verification/ots/pandoc.md @@ -79,8 +79,3 @@ a valid HTML title element, and includes expected document content. **Expected**: FileAssert exits 0 for the user guide HTML document. **Requirement coverage**: `NuGetCache-OTS-Pandoc`. - -### Requirements Coverage - -- **`NuGetCache-OTS-Pandoc`**: Pandoc_BuildNotesHtml, Pandoc_CodeQualityHtml, Pandoc_ReviewPlanHtml, - Pandoc_ReviewReportHtml, Pandoc_DesignHtml, Pandoc_VerificationHtml, Pandoc_UserGuideHtml diff --git a/docs/verification/ots/reqstream.md b/docs/verification/ots/reqstream.md index f2af4f1..d895ca8 100644 --- a/docs/verification/ots/reqstream.md +++ b/docs/verification/ots/reqstream.md @@ -35,7 +35,3 @@ coverage. **Expected**: Exits 0; would exit non-zero if any requirement lacked coverage. **Requirement coverage**: `NuGetCache-OTS-ReqStream`. - -### Requirements Coverage - -- **`NuGetCache-OTS-ReqStream`**: ReqStream_EnforcementMode diff --git a/docs/verification/ots/reviewmark.md b/docs/verification/ots/reviewmark.md index 84c3b6c..c1da37f 100644 --- a/docs/verification/ots/reviewmark.md +++ b/docs/verification/ots/reviewmark.md @@ -43,7 +43,3 @@ report from a test configuration and evidence store. **Expected**: Exits 0 and produces a non-empty review report. **Requirement coverage**: `NuGetCache-OTS-ReviewMark`. - -### Requirements Coverage - -- **`NuGetCache-OTS-ReviewMark`**: ReviewMark_ReviewPlanGeneration, ReviewMark_ReviewReportGeneration diff --git a/docs/verification/ots/sarifmark.md b/docs/verification/ots/sarifmark.md index b54bfa2..8fcc53d 100644 --- a/docs/verification/ots/sarifmark.md +++ b/docs/verification/ots/sarifmark.md @@ -42,7 +42,3 @@ artifacts. **Expected**: Exits 0 and produces a non-empty markdown report. **Requirement coverage**: `NuGetCache-OTS-SarifMark`. - -### Requirements Coverage - -- **`NuGetCache-OTS-SarifMark`**: SarifMark_SarifReading, SarifMark_MarkdownReportGeneration diff --git a/docs/verification/ots/sonarmark.md b/docs/verification/ots/sonarmark.md index e434c62..fcd5510 100644 --- a/docs/verification/ots/sonarmark.md +++ b/docs/verification/ots/sonarmark.md @@ -57,8 +57,3 @@ not retrieve and render quality data correctly. **Expected**: Exits 0 and produces a non-empty markdown quality report. **Requirement coverage**: `NuGetCache-OTS-SonarMark`. - -### Requirements Coverage - -- **`NuGetCache-OTS-SonarMark`**: SonarMark_QualityGateRetrieval, SonarMark_IssuesRetrieval, - SonarMark_HotSpotsRetrieval, SonarMark_MarkdownReportGeneration diff --git a/docs/verification/ots/sysml2tools.md b/docs/verification/ots/sysml2tools.md new file mode 100644 index 0000000..dbea9c2 --- /dev/null +++ b/docs/verification/ots/sysml2tools.md @@ -0,0 +1,53 @@ +## SysML2Tools Verification + +This document provides the verification evidence for the `SysML2Tools` OTS software item. +Requirements for this OTS item are defined in the SysML2Tools OTS Software Requirements document. + +### Required Functionality + +DemaConsulting.SysML2Tools validates the SysML2 architecture model under `docs/sysml2/` for syntax +and reference errors, and renders each view declared in `docs/sysml2/views/design-views.sysml` to +an SVG diagram embedded in the design documentation. Both behaviors run in the same CI pipeline +that produces the compiled Design document, so a successful pipeline run is evidence that +SysML2Tools executed without error. + +### Verification Approach + +SysML2Tools is verified by a combination of authored self-validation evidence and CI pipeline +evidence: + +- **Self-validation evidence**: `dotnet sysml2tools --validate --results ` runs the + tool's built-in self-test suite, which exercises `lint` and `render --format svg` against known + model fixtures and writes a TRX file consumed by `reqstream --enforce` + (`SysML2Tools_LintSelfTest`, `SysML2Tools_RenderSvgSelfTest`). +- **Pipeline evidence**: `lint.ps1` runs `dotnet sysml2tools lint 'docs/sysml2/**/*.sysml'` against + the actual NuGetCacheTool model and fails the build on any syntax or reference error. The + build-docs job runs `dotnet sysml2tools render` to produce one SVG file per declared view under + `docs/design/generated/`. Pandoc then compiles `docs/design/*.md`, which embed those SVG files by + filename; if a declared view failed to render, the missing image reference would cause a broken + image in the compiled Design HTML and PDF. WeasyPrint renders the result to PDF. A CI build + failure at either step is evidence that SysML2Tools did not produce the required model + validation or diagrams against the real model. + +### Test Scenarios + +#### SysML2Tools_LintSelfTest + +**Scenario**: SysML2Tools is invoked with `--validate`, which exercises `lint` against a known-good +and a known-bad model fixture as part of its built-in self-test suite. + +**Expected**: Exits 0 with no reported syntax or reference errors for the valid fixture, and +correctly reports an error for the invalid fixture. + +#### SysML2Tools_RenderSvgSelfTest + +**Scenario**: SysML2Tools is invoked with `--validate`, which exercises `render --format svg` +against a known-good model fixture as part of its built-in self-test suite. + +**Expected**: Exits 0 and produces a non-empty SVG file for the fixture's declared view. + +### Acceptance Criteria + +N/A - Acceptance criteria are managed at the system integration level. This OTS item is considered +verified when the integration test scenarios that exercise its functionality pass in the CI +pipeline. diff --git a/docs/verification/ots/test-results.md b/docs/verification/ots/test-results.md new file mode 100644 index 0000000..82367a0 --- /dev/null +++ b/docs/verification/ots/test-results.md @@ -0,0 +1,46 @@ +## DemaConsulting.TestResults Verification + +This document provides the verification evidence for the `DemaConsulting.TestResults` OTS +software item. Requirements for this OTS item are defined in the DemaConsulting.TestResults +OTS Software Requirements document. + +### Required Functionality + +`DemaConsulting.TestResults` shall serialize self-validation results to TRX (MSTest) and +JUnit XML formats via `TrxSerializer` and `JUnitSerializer`, consumed by `Validation.Run`. +See *DemaConsulting.TestResults Integration Design* for the integration pattern. + +### Verification Approach + +`DemaConsulting.TestResults` is verified through the NuGet Cache Tool's own integration test +suite, which invokes the tool with `--validate --results` and asserts that the expected output +file is created and contains the expected TRX or JUnit elements. + +### Test Environment + +Integration tests run under the standard xUnit v3 test runner within the +`DemaConsulting.NuGet.CacheTool.Tests` project. Tests write results files to a temporary +directory that is cleaned up after each test; no other external services or configuration are +required. + +### Test Scenarios + +#### NuGetCacheTool_ResultsFile_ValidateWithTrxExtension_GeneratesTrxFile + +**Scenario**: The tool is invoked with `--validate --results .trx`. + +**Expected**: `TrxSerializer` creates the results file at the specified path, and the file +content contains the `` and `` tags. + +#### NuGetCacheTool_ResultsFile_ValidateWithXmlExtension_GeneratesJUnitFile + +**Scenario**: The tool is invoked with `--validate --results .xml`. + +**Expected**: `JUnitSerializer` creates the results file at the specified path, and the file +content contains the expected JUnit elements. + +### Acceptance Criteria + +N/A - Acceptance criteria are managed at the system integration level. This OTS item is +considered verified when the integration test scenarios that exercise its functionality pass +in the CI pipeline. diff --git a/docs/verification/ots/versionmark.md b/docs/verification/ots/versionmark.md index d7048e2..eb8dcb8 100644 --- a/docs/verification/ots/versionmark.md +++ b/docs/verification/ots/versionmark.md @@ -40,7 +40,3 @@ VersionMark did not execute correctly. **Expected**: Exits 0 and produces a non-empty versions markdown file. **Requirement coverage**: `NuGetCache-OTS-VersionMark`. - -### Requirements Coverage - -- **`NuGetCache-OTS-VersionMark`**: VersionMark_CapturesVersions, VersionMark_GeneratesMarkdownReport diff --git a/docs/verification/ots/weasyprint.md b/docs/verification/ots/weasyprint.md index 76ab421..197bae3 100644 --- a/docs/verification/ots/weasyprint.md +++ b/docs/verification/ots/weasyprint.md @@ -81,9 +81,3 @@ at least one page, and includes expected document content. **Expected**: FileAssert exits 0 for the user guide PDF document. **Requirement coverage**: `NuGetCache-OTS-WeasyPrint`. - -### Requirements Coverage - -- **`NuGetCache-OTS-WeasyPrint`**: WeasyPrint_BuildNotesPdf, WeasyPrint_CodeQualityPdf, - WeasyPrint_ReviewPlanPdf, WeasyPrint_ReviewReportPdf, WeasyPrint_DesignPdf, - WeasyPrint_VerificationPdf, WeasyPrint_UserGuidePdf diff --git a/docs/verification/ots/xunit.md b/docs/verification/ots/xunit.md index 6cdf88b..6df4c8f 100644 --- a/docs/verification/ots/xunit.md +++ b/docs/verification/ots/xunit.md @@ -88,16 +88,3 @@ summary even when the context is configured for silent operation. **Expected**: xUnit executes the test, the test passes, and the result appears in the TRX output. **Requirement coverage**: `NuGetCache-OTS-xUnit-Execute`, `NuGetCache-OTS-xUnit-Report`. - -### Requirements Coverage - -- **`NuGetCache-OTS-xUnit-Execute`**: Context_Create_NoArguments_ReturnsDefaultContext, - Context_Create_VersionFlag_SetsVersionTrue, Context_Create_SilentFlag_SetsSilentTrue, - Context_Create_LogFlag_OpensLogFile, Context_Create_UnknownArgument_ThrowsArgumentException, - PathHelpers_SafePathCombine_ValidPaths_CombinesCorrectly, - Program_Run_WithVersionFlag_DisplaysVersionOnly, Validation_Run_WithSilentContext_PrintsSummary -- **`NuGetCache-OTS-xUnit-Report`**: Context_Create_NoArguments_ReturnsDefaultContext, - Context_Create_VersionFlag_SetsVersionTrue, Context_Create_SilentFlag_SetsSilentTrue, - Context_Create_LogFlag_OpensLogFile, Context_Create_UnknownArgument_ThrowsArgumentException, - PathHelpers_SafePathCombine_ValidPaths_CombinesCorrectly, - Program_Run_WithVersionFlag_DisplaysVersionOnly, Validation_Run_WithSilentContext_PrintsSummary diff --git a/lint.ps1 b/lint.ps1 index e4d68ac..89b344d 100644 --- a/lint.ps1 +++ b/lint.ps1 @@ -99,7 +99,7 @@ if (-not $skipNpm) { # } # --- DOTNET LINTING SECTION --- -# Runs compliance tools: reqstream, versionmark, reviewmark. +# Runs compliance tools: reqstream, versionmark, reviewmark, sysml2tools. Write-Host "Linting: compliance tools..." $skipDotnetTools = $false dotnet tool restore > $null @@ -114,6 +114,11 @@ if (-not $skipDotnetTools) { dotnet reviewmark --lint if ($LASTEXITCODE -ne 0) { $lintError = $true } + + if (Test-Path docs/sysml2) { + dotnet sysml2tools lint 'docs/sysml2/**/*.sysml' + if ($LASTEXITCODE -ne 0) { $lintError = $true } + } } # [PROJECT-SPECIFIC] Add additional dotnet tool lint checks here. diff --git a/requirements.yaml b/requirements.yaml index 875d6c9..07d6a6e 100644 --- a/requirements.yaml +++ b/requirements.yaml @@ -24,3 +24,4 @@ includes: - docs/reqstream/ots/nuget-caching.yaml - docs/reqstream/ots/test-results.yaml - docs/reqstream/ots/sonarscanner.yaml + - docs/reqstream/ots/sysml2tools.yaml diff --git a/src/DemaConsulting.NuGet.CacheTool/DemaConsulting.NuGet.CacheTool.csproj b/src/DemaConsulting.NuGet.CacheTool/DemaConsulting.NuGet.CacheTool.csproj index eee6441..c82ef93 100644 --- a/src/DemaConsulting.NuGet.CacheTool/DemaConsulting.NuGet.CacheTool.csproj +++ b/src/DemaConsulting.NuGet.CacheTool/DemaConsulting.NuGet.CacheTool.csproj @@ -48,7 +48,7 @@ - + diff --git a/src/DemaConsulting.NuGet.CacheTool/SelfTest/Validation.cs b/src/DemaConsulting.NuGet.CacheTool/SelfTest/Validation.cs index fd6d6e5..bba0a92 100644 --- a/src/DemaConsulting.NuGet.CacheTool/SelfTest/Validation.cs +++ b/src/DemaConsulting.NuGet.CacheTool/SelfTest/Validation.cs @@ -146,6 +146,18 @@ private static void RunHelpTest(Context context, DemaConsulting.TestResults.Test }); } + /// + /// Known package/version used to prove the cache-package self-test actually resolves a + /// real, existing cached package directory rather than merely producing log output. + /// + private const string CachePackageTestId = "DemaConsulting.NuGet.Caching"; + + /// + /// Known package version used alongside for the + /// cache-package self-test. + /// + private const string CachePackageTestVersion = "0.1.0"; + /// /// Runs a test for NuGet package caching functionality. /// @@ -158,19 +170,76 @@ private static void RunCachePackageTest(Context context, DemaConsulting.TestResu testResults, "NuGetCache_CachePackage", "Cache Package Test", - ["DemaConsulting.NuGet.Caching:0.1.0"], + [$"{CachePackageTestId}:{CachePackageTestVersion}"], logContent => { - // Verify that a non-empty path was written to the log - if (!string.IsNullOrWhiteSpace(logContent)) + // The log contains the banner followed by the cached package path as its final + // line (see Program.RunToolLogic), so the path must be extracted from the last + // non-blank line rather than treating the whole log as the path. Verify the log + // contains a cached package path rather than accepting any non-empty output: the + // path must actually exist on disk, and must be named for the requested package id + // and version, so this test genuinely proves that caching produced a valid, usable + // package location. + var packagePath = logContent + .Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries) + .LastOrDefault() + ?.Trim(); + + if (string.IsNullOrWhiteSpace(packagePath)) { - return null; + return "Package path not found in log"; + } + + if (!Directory.Exists(packagePath)) + { + return $"Cached package path '{packagePath}' does not exist on disk"; } - return "Package path not found in log"; + return ValidateCachePackagePath(packagePath, CachePackageTestId, CachePackageTestVersion); }); } + /// + /// Verifies that is named for the exact requested package + /// identity by checking the directory name (version) and its parent directory name + /// (package ID) explicitly, rather than a substring match against the full path. A + /// substring match could produce false positives — for example, version "0.1.0" would + /// also match a path for "0.1.0-beta" or "10.1.0" — which would silently defeat this + /// regression check. Extracted as its own method (rather than an inline check) so it can + /// be exercised directly by unit tests against known-good and known-bad paths, proving + /// the check itself would catch a regression rather than only proving the overall + /// self-test happens to pass today. + /// + /// The resolved cached package directory path. + /// The expected package ID. + /// The expected package version. + /// An error message if the path does not match, or null if it does. + internal static string? ValidateCachePackagePath( + string packagePath, + string expectedPackageId, + string expectedVersion) + { + var trimmedPath = packagePath.TrimEnd('/', '\\'); + + // Path.GetFileName and Path.GetDirectoryName return nullable strings (GetDirectoryName + // returns null for a root path such as "/", and an empty string for a bare relative name + // with no parent segment); declare explicitly as string? so the nullability is + // self-documenting rather than relying on var inference. string.Equals safely treats a + // null or empty candidate as a non-match rather than throwing. + string? versionDirectoryName = Path.GetFileName(trimmedPath); + string? parentDirectory = Path.GetDirectoryName(trimmedPath); + string? packageIdDirectoryName = parentDirectory is null ? null : Path.GetFileName(parentDirectory); + + if (!string.Equals(versionDirectoryName, expectedVersion, StringComparison.OrdinalIgnoreCase) || + !string.Equals(packageIdDirectoryName, expectedPackageId, StringComparison.OrdinalIgnoreCase)) + { + return $"Cached package path '{packagePath}' does not reference the expected " + + $"package '{expectedPackageId}' version '{expectedVersion}'"; + } + + return null; + } + /// /// Runs a validation test with common test execution logic. /// diff --git a/src/DemaConsulting.NuGet.CacheTool/Utilities/PathHelpers.cs b/src/DemaConsulting.NuGet.CacheTool/Utilities/PathHelpers.cs index 8813fcc..2478713 100644 --- a/src/DemaConsulting.NuGet.CacheTool/Utilities/PathHelpers.cs +++ b/src/DemaConsulting.NuGet.CacheTool/Utilities/PathHelpers.cs @@ -26,14 +26,16 @@ namespace DemaConsulting.NuGet.CacheTool.Utilities; internal static class PathHelpers { /// - /// Safely combines two paths, ensuring the resolved combined path stays within the base directory. + /// Safely combines two paths, ensuring is not rooted + /// and that the resolved combined path stays within the base directory. /// /// The base path. /// The relative path to combine. /// The combined path. /// Thrown when or is . /// - /// Thrown when the resolved combined path escapes the base directory, or when a supplied path is invalid. + /// Thrown when is rooted (absolute), when the resolved combined + /// path escapes the base directory, or when a supplied path is invalid. /// /// Thrown when a supplied path contains an unsupported format. /// Thrown when the combined or resolved path exceeds the system-defined maximum length. @@ -43,6 +45,14 @@ internal static string SafePathCombine(string basePath, string relativePath) ArgumentNullException.ThrowIfNull(basePath); ArgumentNullException.ThrowIfNull(relativePath); + // Security check: reject rooted (absolute) relative paths outright, regardless of + // where they would resolve to. This prevents a caller from overriding the base + // directory entirely by supplying an absolute path. + if (Path.IsPathRooted(relativePath)) + { + throw new ArgumentException($"Invalid path component: {relativePath}", nameof(relativePath)); + } + // Combine the paths (preserves the caller's relative/absolute style) var combinedPath = Path.Combine(basePath, relativePath); diff --git a/src/DemaConsulting.NuGet.CacheTool/Utilities/TemporaryDirectory.cs b/src/DemaConsulting.NuGet.CacheTool/Utilities/TemporaryDirectory.cs index 5c27b2a..d7afefb 100644 --- a/src/DemaConsulting.NuGet.CacheTool/Utilities/TemporaryDirectory.cs +++ b/src/DemaConsulting.NuGet.CacheTool/Utilities/TemporaryDirectory.cs @@ -29,6 +29,10 @@ namespace DemaConsulting.NuGet.CacheTool.Utilities; /// /tmp resolving to /private/tmp on macOS, which can cause /// path-comparison failures when the OS returns the real (resolved) path instead /// of the symlink path used to construct it. +/// Instances are NOT thread-safe: and perform +/// file-system operations without any locking or synchronization, so a single instance must +/// not be used concurrently from multiple threads. Each thread or task requiring an isolated +/// temporary directory should create its own instance. /// internal sealed class TemporaryDirectory : IDisposable { diff --git a/test/DemaConsulting.NuGet.CacheTool.Tests/Cli/ContextTests.cs b/test/DemaConsulting.NuGet.CacheTool.Tests/Cli/ContextTests.cs index 794981f..be52ac6 100644 --- a/test/DemaConsulting.NuGet.CacheTool.Tests/Cli/ContextTests.cs +++ b/test/DemaConsulting.NuGet.CacheTool.Tests/Cli/ContextTests.cs @@ -34,6 +34,8 @@ public class ContextTests [Fact] public void Context_Create_NoArguments_ReturnsDefaultContext() { + // Arrange - no setup required, using an empty argument list + // Act using var context = Context.Create([]); @@ -200,8 +202,12 @@ public void Context_Create_LogFlag_OpensLogFile() [Fact] public void Context_Create_UnknownArgument_ThrowsArgumentException() { - // Act & Assert + // Arrange - no setup required, using an unknown argument directly + + // Act var exception = Assert.Throws(() => Context.Create(["--unknown"])); + + // Assert Assert.Contains("Unsupported argument", exception.Message); } @@ -211,7 +217,9 @@ public void Context_Create_UnknownArgument_ThrowsArgumentException() [Fact] public void Context_Create_PackageArgument_AddsToPackagesList() { - // Arrange & Act + // Arrange - no setup required, using a package argument directly + + // Act using var context = Context.Create(["DemaConsulting.NuGet.Caching:0.1.0"]); // Assert @@ -306,8 +314,13 @@ public void Context_WriteError_SetsErrorExitCode() [Fact] public void Context_Create_WithoutColonInPackage_ThrowsArgumentException() { - // Act & Assert - Assert.Throws(() => Context.Create(["notapackage"])); + // Arrange - no setup required, using a malformed package argument directly + + // Act + var exception = Assert.Throws(() => Context.Create(["notapackage"])); + + // Assert + Assert.Contains("Unsupported argument", exception.Message); } /// @@ -343,8 +356,12 @@ public void Context_WriteError_NotSilent_WritesToConsole() [Fact] public void Context_Create_LogFlag_WithoutValue_ThrowsArgumentException() { - // Act & Assert + // Arrange - no setup required, using the --log flag without a value + + // Act var exception = Assert.Throws(() => Context.Create(["--log"])); + + // Assert Assert.Contains("--log", exception.Message); } @@ -354,8 +371,12 @@ public void Context_Create_LogFlag_WithoutValue_ThrowsArgumentException() [Fact] public void Context_Create_ResultsFlag_WithoutValue_ThrowsArgumentException() { - // Act & Assert + // Arrange - no setup required, using the --results flag without a value + + // Act var exception = Assert.Throws(() => Context.Create(["--results"])); + + // Assert Assert.Contains("--results", exception.Message); } diff --git a/test/DemaConsulting.NuGet.CacheTool.Tests/IntegrationTests.cs b/test/DemaConsulting.NuGet.CacheTool.Tests/IntegrationTests.cs index 5a0762c..63b3734 100644 --- a/test/DemaConsulting.NuGet.CacheTool.Tests/IntegrationTests.cs +++ b/test/DemaConsulting.NuGet.CacheTool.Tests/IntegrationTests.cs @@ -113,8 +113,8 @@ public void NuGetCacheTool_SelfValidation_ValidateFlagProvided_RunsValidation() public void NuGetCacheTool_ResultsFile_ValidateWithTrxExtension_GeneratesTrxFile() { // Arrange - var resultsFile = Path.GetTempFileName(); - resultsFile = Path.ChangeExtension(resultsFile, ".trx"); + var originalTempFile = Path.GetTempFileName(); + var resultsFile = Path.ChangeExtension(originalTempFile, ".trx"); try { @@ -141,6 +141,11 @@ public void NuGetCacheTool_ResultsFile_ValidateWithTrxExtension_GeneratesTrxFile { File.Delete(resultsFile); } + + if (File.Exists(originalTempFile)) + { + File.Delete(originalTempFile); + } } } @@ -151,8 +156,8 @@ public void NuGetCacheTool_ResultsFile_ValidateWithTrxExtension_GeneratesTrxFile public void NuGetCacheTool_ResultsFile_ValidateWithXmlExtension_GeneratesJUnitFile() { // Arrange - var resultsFile = Path.GetTempFileName(); - resultsFile = Path.ChangeExtension(resultsFile, ".xml"); + var originalTempFile = Path.GetTempFileName(); + var resultsFile = Path.ChangeExtension(originalTempFile, ".xml"); try { @@ -180,6 +185,11 @@ public void NuGetCacheTool_ResultsFile_ValidateWithXmlExtension_GeneratesJUnitFi { File.Delete(resultsFile); } + + if (File.Exists(originalTempFile)) + { + File.Delete(originalTempFile); + } } } diff --git a/test/DemaConsulting.NuGet.CacheTool.Tests/ProgramTests.cs b/test/DemaConsulting.NuGet.CacheTool.Tests/ProgramTests.cs index e8ccd15..7a8fcc0 100644 --- a/test/DemaConsulting.NuGet.CacheTool.Tests/ProgramTests.cs +++ b/test/DemaConsulting.NuGet.CacheTool.Tests/ProgramTests.cs @@ -187,6 +187,8 @@ public void Program_Run_WithPackageArgument_CachesPackage() [Fact] public void Program_Version_ReturnsNonEmptyString() { + // Arrange - no setup required, reading a static property + // Act var version = Program.Version; diff --git a/test/DemaConsulting.NuGet.CacheTool.Tests/SelfTest/SelfTestTests.cs b/test/DemaConsulting.NuGet.CacheTool.Tests/SelfTest/SelfTestTests.cs index 1b8aa4f..e1f4866 100644 --- a/test/DemaConsulting.NuGet.CacheTool.Tests/SelfTest/SelfTestTests.cs +++ b/test/DemaConsulting.NuGet.CacheTool.Tests/SelfTest/SelfTestTests.cs @@ -151,4 +151,24 @@ public void SelfTest_ResultsFile_GeneratesJUnitFile() } } + /// + /// Test that the SelfTest subsystem reports an error and writes no results file when + /// an unsupported --results file extension is specified. + /// + [Fact] + public void SelfTest_ResultsFile_UnsupportedExtension_ReportsErrorAndSkipsFile() + { + // Arrange: prepare a results file path with an unsupported extension + using var temporaryDirectory = new TemporaryDirectory(); + var resultsFile = temporaryDirectory.GetFilePath($"{Guid.NewGuid():N}.json"); + using var context = Context.Create(["--validate", "--silent", "--results", resultsFile]); + + // Act: run self-validation requesting an unsupported results format + Validation.Run(context); + + // Assert: the unsupported extension is reported as an error and no file is written + Assert.NotEqual(0, context.ExitCode); + Assert.False(File.Exists(resultsFile), "Results file should not be created for an unsupported extension"); + } + } diff --git a/test/DemaConsulting.NuGet.CacheTool.Tests/SelfTest/ValidationTests.cs b/test/DemaConsulting.NuGet.CacheTool.Tests/SelfTest/ValidationTests.cs index 7332c46..dfd8377 100644 --- a/test/DemaConsulting.NuGet.CacheTool.Tests/SelfTest/ValidationTests.cs +++ b/test/DemaConsulting.NuGet.CacheTool.Tests/SelfTest/ValidationTests.cs @@ -113,6 +113,143 @@ public void Validation_Run_UnsupportedResultsFormat_ReportsError() Assert.NotEqual(0, context.ExitCode); } + /// + /// Test that Run's cache-package self-test passes because it resolves a real, existing + /// cached package directory - not merely because some non-whitespace text was logged. + /// + [Fact] + public void Validation_Run_CachePackageSelfTest_PassesWithRealCachedPackagePath() + { + // Arrange: redirect stdout to capture validation output + var originalOut = Console.Out; + try + { + using var outWriter = new StringWriter(); + Console.SetOut(outWriter); + using var context = Context.Create(["--validate"]); + + // Act: run self-validation, which includes the cache-package self-test + Validation.Run(context); + + // Assert: the cache-package self-test reports success and the overall run passes + var output = outWriter.ToString(); + Assert.Contains("Cache Package Test - PASSED", output); + Assert.Equal(0, context.ExitCode); + } + finally + { + Console.SetOut(originalOut); + } + } + + /// + /// Test that ValidateCachePackagePath accepts a path whose directory name and parent + /// directory name exactly match the expected version and package ID. + /// + [Fact] + public void Validation_ValidateCachePackagePath_ExactMatch_ReturnsNull() + { + // Arrange: a path exactly matching the expected package id and version + var path = Path.Combine("packages", "demaconsulting.nuget.caching", "0.1.0"); + + // Act: validate the path against the expected identity + var result = Validation.ValidateCachePackagePath(path, "DemaConsulting.NuGet.Caching", "0.1.0"); + + // Assert: no error is reported + Assert.Null(result); + } + + /// + /// Test that ValidateCachePackagePath rejects a path whose version directory merely + /// contains the expected version as a substring (e.g. a pre-release suffix), proving the + /// check is an exact match rather than a substring match. + /// + [Fact] + public void Validation_ValidateCachePackagePath_VersionSuffixSubstringMatch_ReturnsError() + { + // Arrange: a version directory that contains, but is not equal to, the expected version + var path = Path.Combine("packages", "demaconsulting.nuget.caching", "0.1.0-beta"); + + // Act: validate the path against the expected identity + var result = Validation.ValidateCachePackagePath(path, "DemaConsulting.NuGet.Caching", "0.1.0"); + + // Assert: an error is reported since the version does not exactly match + Assert.NotNull(result); + } + + /// + /// Test that ValidateCachePackagePath rejects a path whose version directory merely + /// contains the expected version as a substring (e.g. a higher major version), proving the + /// check is an exact match rather than a substring match. + /// + [Fact] + public void Validation_ValidateCachePackagePath_VersionPrefixSubstringMatch_ReturnsError() + { + // Arrange: a version directory that contains, but is not equal to, the expected version + var path = Path.Combine("packages", "demaconsulting.nuget.caching", "10.1.0"); + + // Act: validate the path against the expected identity + var result = Validation.ValidateCachePackagePath(path, "DemaConsulting.NuGet.Caching", "0.1.0"); + + // Assert: an error is reported since the version does not exactly match + Assert.NotNull(result); + } + + /// + /// Test that ValidateCachePackagePath rejects a path whose parent directory does not + /// match the expected package ID. + /// + [Fact] + public void Validation_ValidateCachePackagePath_WrongPackageId_ReturnsError() + { + // Arrange: a path for a different package ID + var path = Path.Combine("packages", "some.other.package", "0.1.0"); + + // Act: validate the path against the expected identity + var result = Validation.ValidateCachePackagePath(path, "DemaConsulting.NuGet.Caching", "0.1.0"); + + // Assert: an error is reported since the package ID does not match + Assert.NotNull(result); + } + + /// + /// Test that ValidateCachePackagePath rejects a root path with no parent directory + /// segment at all ( returns null for a root + /// such as "/"), proving the null-parent-directory branch is handled as a non-match + /// rather than throwing. + /// + [Fact] + public void Validation_ValidateCachePackagePath_NoParentDirectory_ReturnsError() + { + // Arrange: a root path, which has no parent directory segment + const string path = "/"; + + // Act: validate the path against the expected identity + var result = Validation.ValidateCachePackagePath(path, "DemaConsulting.NuGet.Caching", "0.1.0"); + + // Assert: an error is reported since there is no package ID directory to match + Assert.NotNull(result); + } + + /// + /// Test that ValidateCachePackagePath rejects a bare relative directory name with no + /// path separator ( returns an empty string, + /// not null, for this case), proving the empty-parent-directory-name case is handled as + /// a non-match. + /// + [Fact] + public void Validation_ValidateCachePackagePath_BareRelativeName_ReturnsError() + { + // Arrange: a bare directory name with no parent path segment + const string path = "0.1.0"; + + // Act: validate the path against the expected identity + var result = Validation.ValidateCachePackagePath(path, "DemaConsulting.NuGet.Caching", "0.1.0"); + + // Assert: an error is reported since the (empty) package ID directory does not match + Assert.NotNull(result); + } + /// /// Test that Run prints a summary containing total, passed, and failed counts. /// diff --git a/test/DemaConsulting.NuGet.CacheTool.Tests/Utilities/PathHelpersTests.cs b/test/DemaConsulting.NuGet.CacheTool.Tests/Utilities/PathHelpersTests.cs index 0bdd2c9..6c1139e 100644 --- a/test/DemaConsulting.NuGet.CacheTool.Tests/Utilities/PathHelpersTests.cs +++ b/test/DemaConsulting.NuGet.CacheTool.Tests/Utilities/PathHelpersTests.cs @@ -55,9 +55,11 @@ public void PathHelpers_SafePathCombine_PathTraversalWithDoubleDots_ThrowsArgume var basePath = "/home/user/project"; var relativePath = "../etc/passwd"; - // Act & Assert - var exception = Assert.Throws(() => - PathHelpers.SafePathCombine(basePath, relativePath)); + // Act + var act = () => PathHelpers.SafePathCombine(basePath, relativePath); + + // Assert + var exception = Assert.Throws(act); Assert.Contains("Invalid path component", exception.Message); } @@ -71,9 +73,11 @@ public void PathHelpers_SafePathCombine_DoubleDotsInMiddle_ThrowsArgumentExcepti var basePath = "/home/user/project"; var relativePath = "subfolder/../../../etc/passwd"; - // Act & Assert - var exception = Assert.Throws(() => - PathHelpers.SafePathCombine(basePath, relativePath)); + // Act + var act = () => PathHelpers.SafePathCombine(basePath, relativePath); + + // Assert + var exception = Assert.Throws(act); Assert.Contains("Invalid path component", exception.Message); } @@ -85,10 +89,13 @@ public void PathHelpers_SafePathCombine_UnixAbsolutePath_ThrowsArgumentException { // Arrange const string basePath = "/tmp/base"; + const string relativePath = "/etc/passwd"; + + // Act + var act = () => PathHelpers.SafePathCombine(basePath, relativePath); - // Act & Assert - Assert.Throws(() => - PathHelpers.SafePathCombine(basePath, "/etc/passwd")); + // Assert + Assert.Throws(act); } /// @@ -105,10 +112,33 @@ public void PathHelpers_SafePathCombine_WindowsAbsolutePath_ThrowsArgumentExcept // Arrange const string basePath = "/tmp/base"; + const string relativePath = @"C:\Windows\System32"; - // Act & Assert - Assert.Throws(() => - PathHelpers.SafePathCombine(basePath, @"C:\Windows\System32")); + // Act + var act = () => PathHelpers.SafePathCombine(basePath, relativePath); + + // Assert + Assert.Throws(act); + } + + /// + /// Test that SafePathCombine throws ArgumentException for a rooted relative path that + /// would still resolve inside the base directory, proving rooted paths are rejected + /// upfront regardless of where they would resolve to. + /// + [Fact] + public void PathHelpers_SafePathCombine_RootedPathInsideBase_ThrowsArgumentException() + { + // Arrange: a rooted relative path that already resolves inside the base directory + var basePath = Path.GetTempPath(); + var relativePath = Path.Combine(basePath, "child.txt"); + + // Act + var act = () => PathHelpers.SafePathCombine(basePath, relativePath); + + // Assert + var exception = Assert.Throws(act); + Assert.Contains("Invalid path component", exception.Message); } /// @@ -185,9 +215,14 @@ public void PathHelpers_SafePathCombine_DoubleDotPrefix_CombinesCorrectly() [Fact] public void PathHelpers_SafePathCombine_NullBase_ThrowsArgumentNullException() { - // Act: verify that a null base path throws ArgumentNullException - Assert.Throws(() => - PathHelpers.SafePathCombine(null!, "relative/path")); + // Arrange + const string relativePath = "relative/path"; + + // Act + var act = () => PathHelpers.SafePathCombine(null!, relativePath); + + // Assert + Assert.Throws(act); } /// @@ -196,8 +231,13 @@ public void PathHelpers_SafePathCombine_NullBase_ThrowsArgumentNullException() [Fact] public void PathHelpers_SafePathCombine_NullRelative_ThrowsArgumentNullException() { - // Act: verify that a null relative path throws ArgumentNullException - Assert.Throws(() => - PathHelpers.SafePathCombine("/base/path", null!)); + // Arrange + const string basePath = "/base/path"; + + // Act + var act = () => PathHelpers.SafePathCombine(basePath, null!); + + // Assert + Assert.Throws(act); } } diff --git a/test/DemaConsulting.NuGet.CacheTool.Tests/Utilities/TemporaryDirectoryTests.cs b/test/DemaConsulting.NuGet.CacheTool.Tests/Utilities/TemporaryDirectoryTests.cs index 54341eb..5c0a7eb 100644 --- a/test/DemaConsulting.NuGet.CacheTool.Tests/Utilities/TemporaryDirectoryTests.cs +++ b/test/DemaConsulting.NuGet.CacheTool.Tests/Utilities/TemporaryDirectoryTests.cs @@ -32,7 +32,7 @@ public class TemporaryDirectoryTests /// Test that the constructor creates the directory on disk. /// [Fact] - public void TemporaryDirectory_Constructor_CreatesDirectory() + public void TemporaryDirectory_Constructor_WhenCalled_CreatesDirectoryOnDisk() { // Act using var tmpDir = new TemporaryDirectory(); @@ -46,7 +46,7 @@ public void TemporaryDirectory_Constructor_CreatesDirectory() /// Test that two instances produce distinct directory paths. /// [Fact] - public void TemporaryDirectory_Constructor_CreatesUniqueDirectories() + public void TemporaryDirectory_Constructor_MultipleInstances_CreatesUniqueDirectories() { // Act using var tmpDir1 = new TemporaryDirectory(); @@ -99,27 +99,40 @@ public void TemporaryDirectory_GetFilePath_TraversalAttempt_ThrowsArgumentExcept // Arrange using var tmpDir = new TemporaryDirectory(); - // Act + Assert - Assert.Throws(() => tmpDir.GetFilePath("../escaped.txt")); + // Act + var act = () => tmpDir.GetFilePath("../escaped.txt"); + + // Assert + Assert.Throws(act); } /// /// Test that Dispose deletes the temporary directory and its contents. /// [Fact] - public void TemporaryDirectory_Dispose_DeletesDirectory() + public void TemporaryDirectory_Dispose_WhenCalled_DeletesDirectory() { // Arrange - string dirPath; - using (var tmpDir = new TemporaryDirectory()) + var tmpDir = new TemporaryDirectory(); + var dirPath = tmpDir.DirectoryPath; + try { - dirPath = tmpDir.DirectoryPath; File.WriteAllText(tmpDir.GetFilePath("file.txt"), "content"); - } - // Assert - Assert.False(Directory.Exists(dirPath), - "Directory should be deleted after disposal."); + // Act + tmpDir.Dispose(); + + // Assert + Assert.False(Directory.Exists(dirPath), + "Directory should be deleted after disposal."); + } + finally + { + // Ensure cleanup even if an assertion or setup step throws before Dispose() runs. + // Dispose() itself suppresses non-fatal IO/permission errors and is safe to call + // twice, so route cleanup through it rather than deleting the directory directly. + tmpDir.Dispose(); + } } /// @@ -132,8 +145,11 @@ public void TemporaryDirectory_Dispose_AlreadyDeleted_DoesNotThrow() var tmpDir = new TemporaryDirectory(); Directory.Delete(tmpDir.DirectoryPath, recursive: true); - // Act + Assert: second disposal should not throw - var exception = Record.Exception(() => tmpDir.Dispose()); + // Act + var act = () => tmpDir.Dispose(); + + // Assert: second disposal should not throw + var exception = Record.Exception(act); Assert.Null(exception); } } diff --git a/test/DemaConsulting.NuGet.CacheTool.Tests/Utilities/UtilitiesTests.cs b/test/DemaConsulting.NuGet.CacheTool.Tests/Utilities/UtilitiesTests.cs index b897ecd..8f5ded0 100644 --- a/test/DemaConsulting.NuGet.CacheTool.Tests/Utilities/UtilitiesTests.cs +++ b/test/DemaConsulting.NuGet.CacheTool.Tests/Utilities/UtilitiesTests.cs @@ -90,8 +90,11 @@ public void Utilities_PathTraversal_TraversalAttempt_ThrowsArgumentException() // Arrange: create a temporary directory on the real filesystem using var tmpDir = new TemporaryDirectory(); - // Act / Assert: a traversal path must be rejected before any filesystem access - Assert.Throws(() => tmpDir.GetFilePath("../escape.txt")); + // Act + var act = () => tmpDir.GetFilePath("../escape.txt"); + + // Assert: a traversal path must be rejected before any filesystem access + Assert.Throws(act); } /// @@ -102,23 +105,31 @@ public void Utilities_PathTraversal_TraversalAttempt_ThrowsArgumentException() public void Utilities_DirectoryLifecycle_CreateAndDispose_DirectoryCreatedThenDeleted() { // Arrange: create the temporary directory and capture its path before disposal - string capturedPath; - using (var tmpDir = new TemporaryDirectory()) + var tmpDir = new TemporaryDirectory(); + var capturedPath = tmpDir.DirectoryPath; + try { - capturedPath = tmpDir.DirectoryPath; - - // Act: write a file into the directory via GetFilePath to prove the directory is usable + // Act: write a file into the directory via GetFilePath, then dispose the directory var filePath = tmpDir.GetFilePath("sentinel.txt"); File.WriteAllText(filePath, "lifecycle test"); - - Assert.True(Directory.Exists(capturedPath), "Directory must exist before disposal"); - Assert.True(File.Exists(filePath), "File written via GetFilePath must be accessible"); + var existedBeforeDisposal = Directory.Exists(capturedPath); + var fileExistedBeforeDisposal = File.Exists(filePath); + tmpDir.Dispose(); + + // Assert: the directory was usable before disposal, and is gone afterward + Assert.True(existedBeforeDisposal, "Directory must exist before disposal"); + Assert.True(fileExistedBeforeDisposal, "File written via GetFilePath must be accessible"); + Assert.False( + Directory.Exists(capturedPath), + $"Directory '{capturedPath}' should have been deleted by Dispose"); + } + finally + { + // Ensure cleanup even if an assertion or setup step throws before Dispose() runs. + // Dispose() itself suppresses non-fatal IO/permission errors and is safe to call + // twice, so route cleanup through it rather than deleting the directory directly. + tmpDir.Dispose(); } - - // Assert: the directory (and its contents) must be gone after the using block closes - Assert.False( - Directory.Exists(capturedPath), - $"Directory '{capturedPath}' should have been deleted by Dispose"); } ///