From f4d611b77cc70b4686506d44bf8f439045d9e0d2 Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Tue, 6 Oct 2026 12:35:45 +0800 Subject: [PATCH] Add the Design Class Diagrams DCD-001 and DCD-002, align SD-001 - DCD-001 (UC-001): design classes refining DM-001, with class table, method traceability to OC-001 and SD-001, patterns and dependency check, and the mapping of each class to src/lib. - DCD-002: the consolidated project-level model, created from DCD-001. - SD-001: messages aligned with the DCD method signatures; cites DCD-001. - Dictionary, registry and traceability matrix updated. Co-Authored-By: Claude Sonnet 5.5 --- docs/artifact-registry.md | 1 + docs/dcd.md | 312 +++++++++++++++++++++++++++++++ docs/dictionary.md | 40 ++-- docs/sqa/traceability-matrix.md | 15 +- docs/uc-001/dcd.md | 315 ++++++++++++++++++++++++++++++++ docs/uc-001/sd.md | 31 ++-- 6 files changed, 675 insertions(+), 39 deletions(-) create mode 100644 docs/dcd.md create mode 100644 docs/uc-001/dcd.md diff --git a/docs/artifact-registry.md b/docs/artifact-registry.md index 04feb2f..7ababfb 100644 --- a/docs/artifact-registry.md +++ b/docs/artifact-registry.md @@ -21,6 +21,7 @@ document of a type. `Primary File` may contain a glob (e.g. | OC | Operation Contract | docs/uc-*/oc.md | 002 | | SD | Sequence Diagram | docs/uc-*/sd.md | 002 | | DM | Domain Model | docs/domain-model.md | 003 | +| DCD | Design Class Diagram | docs/dcd.md | 003 | | DICT | Domain Dictionary (PO and IT terms) | docs/dictionary.md | 002 | | UCD | Use Case Diagram | docs/use-case-diagram.md | 002 | | RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 020 | diff --git a/docs/dcd.md b/docs/dcd.md new file mode 100644 index 0000000..021c7bc --- /dev/null +++ b/docs/dcd.md @@ -0,0 +1,312 @@ +# Design Class Diagram + +## Metadata +| Key | Value | +| --- | --- | +| ID | DCD-002 | +| CrossReference | [DCD-001], [DM-002], [UC-001], [OC-001], [SD-001], [DICT-001] | + +## Version History +| Date | Status | Author | Reviewer | Change | Commit | +| --- | --- | --- | --- | --- | --- | +| 2026-10-06 | Proposed | Jens Tirsvad Nielsen | S02 | Initial version, from DCD-001 (UC-001) | pending | + +--- + +## Purpose and Scope + +The consolidated design model of the project. Use-case models are scoped views; when one changes, this model is checked and updated in the same change. It currently covers [UC-001] "Create a new project" ([DCD-001]), which it was created from, and refines the concepts of [DM-002]. Class and attribute names are the IT terms of [DICT-001]. Every method traces to a contract in [OC-001] or a message in [SD-001]. + +The classes are design classes of a Bash program: a class is a group of functions in `src/lib/` with its data held in the shared state arrays (see "Implementation Mapping"). + +## Diagram + +```plantuml +@startuml +skinparam classAttributeIconSize 0 +hide empty members + +enum Visibility { + private + public +} + +class ProjectCreator <> { + +startProjectCreation() : PromptSet + +provideProjectDetails(name : String, description : String, visibility : Visibility, giteaOwner : Owner, githubOwner : Owner [0..1], directory : Path, enablePlanGate : Boolean) : Summary +} +class ConfigLoader { + +load(configFile : Path, envFile : Path) : Configuration +} +class ToolChecker { + +check(tools : String [1..*]) : ToolCheck +} +class Preflight { + +check(request : ProjectRequest) : PreflightResult +} +abstract class GitHost <> { + -name : String + -webAddress : String + -apiAddress : String + +verifyToken() : Boolean + +ownerAccepts(owner : Owner) : Boolean + +nameFree(name : String) : Boolean +} +class GiteaClient <> { + +GiteaClient(configuration : Configuration) + +hasLicense(key : String) : Boolean + +createRepository(request : ProjectRequest, license : String [0..1]) : GiteaRepository + +addPushMirror(source : GiteaRepository, target : GitHubRepository) : PushMirror + -requestSync(mirror : PushMirror) : void +} +class GitHubClient <> { + +GitHubClient(configuration : Configuration) + +createEmptyRepository(request : ProjectRequest) : GitHubRepository +} +class LocalProjectBuilder { + +build(directory : Path, source : GiteaRepository, target : GitHubRepository [0..1], sshPassed : Boolean) : LocalProject +} +class FrameworkInstaller { + +install(project : LocalProject, enablePlanGate : Boolean) : InstallResult +} +class SummaryReport { + +compose(request : ProjectRequest) : Summary +} + +class Run { + -isApply : Boolean +} +class Configuration { + -giteaUrl : String + -giteaApiUrl : String + -githubWebUrl : String + -githubApiUrl : String + -giteaSshPort : Integer + -mirrorInterval : String + -frameworkRepo : String + -presetDetails : Map [0..1] +} +class Credential { + -kind : String + -value : String +} +class ToolCheck { + -hasGit : Boolean + -hasCurl : Boolean + -hasJq : Boolean +} +class PromptSet { + -prompts : String [1..*] +} +class ProjectRequest { + -name : String + -description : String + -visibility : Visibility + -directory : Path + -enablePlanGate : Boolean +} +class Owner { + -name : String + -kind : String +} +class PreflightResult { + -tokensWork : Boolean + -ownersAccept : Boolean + -nameIsFree : Boolean + -licenseIsOffered : Boolean + -sshPassed : Boolean +} +abstract class Repository { + -name : String + -description : String + -visibility : Visibility + -address : String +} +class GiteaRepository +class GitHubRepository +class LicenseFile { + -key : String +} +class PushMirror { + -interval : String + -syncOnCommit : Boolean +} +class LocalProject { + -directory : Path +} +class Remote { + -name : String + -address : String +} +class Submodule { + -name : String + -address : String +} +class HookSetup { + -areSkillsInstalled : Boolean + -areHooksInstalled : Boolean + -isPlanGateEnabled : Boolean +} +class Template { + -name : String + -isCopied : Boolean +} +class InstallResult <> +class Summary { + -createdItems : String [0..*] + -skippedItems : String [0..*] + -nextSteps : String [0..*] +} + +ProjectCreator ..> ConfigLoader : creates +ProjectCreator ..> ToolChecker : creates +ProjectCreator ..> Preflight : creates +ProjectCreator ..> GiteaClient : creates +ProjectCreator ..> GitHubClient : creates [0..1] +ProjectCreator ..> LocalProjectBuilder : creates +ProjectCreator ..> FrameworkInstaller : creates +ProjectCreator ..> SummaryReport : creates +Preflight ..> GiteaClient : asks +Preflight ..> GitHubClient : asks [0..1] +GitHost <|-- GiteaClient +GitHost <|-- GitHubClient +GitHost "1" --> "0..*" Owner : has +GiteaClient ..> Configuration +GitHubClient ..> Configuration + +ProjectCreator --> "1" Run +Run *-- "1" Configuration +Run *-- "1" ToolCheck +Run *-- "0..1" ProjectRequest +Run --> "1" PromptSet : returns +Configuration *-- "1..2" Credential + +ProjectRequest --> "1" Owner : giteaOwner +ProjectRequest --> "0..1" Owner : githubOwner +ProjectRequest *-- "0..1" PreflightResult +ProjectRequest --> "0..1" GiteaRepository : stored in +ProjectRequest --> "0..1" GitHubRepository : also stored in +ProjectRequest --> "0..1" LocalProject : working copy +Summary --> "1" ProjectRequest : reports on + +Repository <|-- GiteaRepository +Repository <|-- GitHubRepository +Repository --> "1" Owner : owned by +GiteaRepository *-- "0..1" LicenseFile +PushMirror --> "1" GiteaRepository : source +PushMirror --> "1" GitHubRepository : target +PushMirror --> "1" Credential : authorised by + +LocalProject *-- "1..2" Remote +Remote --> "1" Repository : points to +LocalProject *-- "1" Submodule +LocalProject *-- "1" HookSetup +LocalProject *-- "0..*" Template +InstallResult --> "1" Submodule +InstallResult --> "1" HookSetup +InstallResult --> "0..*" Template + +ProjectRequest --> Visibility +Repository --> Visibility +@enduml +``` + +## Class Table + +| Class | Refines (Domain Model concept) | Responsibility | Attributes | Operations | +| --- | --- | --- | --- | --- | +| `ProjectCreator` | none (controller for the system operations of [OC-001]) | Receives the two system operations, sequences the steps and stops on the first failure. | none | `startProjectCreation`, `provideProjectDetails` | +| `ConfigLoader` | Configuration | Reads `config.env` and `.env` as plain text and validates every value, preset project details included. | none | `load` | +| `ToolChecker` | none (system concept `ToolCheck`) | Detects the required and optional tools. | none | `check` | +| `Preflight` | none (system concept `PreflightResult`) | Runs the read-only checks of both hosts before anything is created. | none | `check` | +| `GitHost` | Git Host | The operations every host offers: check the token, check that an owner accepts new repositories, check that a name is free. | `name`, `webAddress`, `apiAddress` | `verifyToken`, `ownerAccepts`, `nameFree` | +| `GiteaClient` | Git Host (Gitea) | Hides the Gitea API and its token; creates the repository and the push mirror. | none beyond `GitHost` (uses `Configuration`) | `GiteaClient`, `hasLicense`, `createRepository`, `addPushMirror`, `requestSync` | +| `GitHubClient` | Git Host (GitHub) | Hides the GitHub API and its token; creates the empty repository. | none beyond `GitHost` (uses `Configuration`) | `GitHubClient`, `createEmptyRepository` | +| `LocalProjectBuilder` | Local Project, Remote | Creates the project directory, its git repository and its credential-free remotes. | none | `build` | +| `FrameworkInstaller` | Framework, Framework Setup, Template | Adds the framework submodule, installs skills and hooks once, and copies the templates without overwriting. | none | `install` | +| `SummaryReport` | Summary | Composes the report of what was created, skipped or failed. | none | `compose` | +| `Run` | none (system concept) | Holds the state of one execution. | `isApply` | none | +| `Configuration` | Configuration | Holds the service addresses, the credentials and any preset project details. | `giteaUrl`, `giteaApiUrl`, `githubWebUrl`, `githubApiUrl`, `giteaSshPort`, `mirrorInterval`, `frameworkRepo`, `presetDetails` | none | +| `Credential` | Access Token | Holds a secret in memory only; it never becomes part of an address or a message. | `kind`, `value` | none | +| `ToolCheck` | none (system concept) | Records which tools are present. | `hasGit`, `hasCurl`, `hasJq` | none | +| `PromptSet` | none (system concept) | The questions still to ask; a detail preset in `config.env` is not in it. | `prompts` | none | +| `ProjectRequest` | Project | Holds the details of the project being created. | `name`, `description`, `visibility`, `directory`, `enablePlanGate` | none | +| `Owner` | Owner | A user or organization on a host. | `name`, `kind` | none | +| `PreflightResult` | none (system concept) | Records the outcome of the preflight checks. | `tokensWork`, `ownersAccept`, `nameIsFree`, `licenseIsOffered`, `sshPassed` | none | +| `Repository` | Repository | Common data of a repository on a host. | `name`, `description`, `visibility`, `address` | none | +| `GiteaRepository` | Gitea Repository | The source of truth. | none beyond `Repository` | none | +| `GitHubRepository` | GitHub Repository | Receives its content from the mirror. | none beyond `Repository` | none | +| `LicenseFile` | License | The `AGPL-3.0` file in the Gitea repository when GitHub is chosen. | `key` | none | +| `PushMirror` | Mirror | The Gitea to GitHub push mirror. | `interval`, `syncOnCommit` | none | +| `LocalProject` | Local Project | The project directory on the Maintainer's machine. | `directory` | none | +| `Remote` | Remote | A named link to a repository (`origin`, `github`), without a credential. | `name`, `address` | none | +| `Submodule` | Framework | The framework added to the local project. | `name`, `address` | none | +| `HookSetup` | Framework Setup | Records the skills and hooks installed and the plan gate state. | `areSkillsInstalled`, `areHooksInstalled`, `isPlanGateEnabled` | none | +| `Template` | Template | A framework file copied into the project. | `name`, `isCopied` | none | +| `InstallResult` | none (carries the result of one operation) | Returns the submodule, the hook setup and the templates of `install`. | none | none | +| `Summary` | Summary | The report returned to the Maintainer; it contains no credential. | `createdItems`, `skippedItems`, `nextSteps` | none | +| `Visibility` | none (enumeration of a Project and Repository attribute) | The two allowed visibilities. | `private`, `public` | none | + +## Method Traceability + +| Method signature | Operation Contract / SD message | +| --- | --- | +| `ProjectCreator.startProjectCreation() : PromptSet` | [OC-001] `startProjectCreation`; [SD-001] `startProjectCreation()` | +| `ProjectCreator.provideProjectDetails(name, description, visibility, giteaOwner, githubOwner, directory, enablePlanGate) : Summary` | [OC-001] `provideProjectDetails`; [SD-001] `provideProjectDetails(...)` | +| `ConfigLoader.load(configFile, envFile) : Configuration` | [SD-001] `load(config.env, .env)`; [OC-001] `startProjectCreation` P2 | +| `ToolChecker.check(tools) : ToolCheck` | [SD-001] `check(git, curl, jq)`; [OC-001] `startProjectCreation` P3 | +| `Preflight.check(request) : PreflightResult` | [SD-001] `check(request)`; [OC-001] `provideProjectDetails` P2 | +| `GiteaClient(configuration)` | [SD-001] `new(configuration)` to `GiteaClient` | +| `GitHost.verifyToken() : Boolean` | [SD-001] `verifyToken()` from `Preflight` to either client; P2 | +| `GitHost.ownerAccepts(owner) : Boolean` | [SD-001] `ownerAccepts(giteaOwner)` and `ownerAccepts(githubOwner)`; P2 | +| `GitHost.nameFree(name) : Boolean` | [SD-001] `nameFree(name)` to either client; P2 | +| `GiteaClient.hasLicense(key) : Boolean` | [SD-001] `hasLicense(AGPL-3.0)`; P2 | +| `GiteaClient.createRepository(request, license) : GiteaRepository` | [SD-001] `createRepository(request, license)`; P3, P4 | +| `GiteaClient.addPushMirror(source, target) : PushMirror` | [SD-001] `addPushMirror(giteaRepository, gitHubRepository)`; P6 | +| `GiteaClient.requestSync(mirror) : void` | [SD-001] `requestSync(pushMirror)`; P6 | +| `GitHubClient(configuration)` | [SD-001] `new(configuration)` to `GitHubClient` | +| `GitHubClient.createEmptyRepository(request) : GitHubRepository` | [SD-001] `createEmptyRepository(request)`; P5 | +| `LocalProjectBuilder.build(directory, source, target, sshPassed) : LocalProject` | [SD-001] `build(directory, giteaRepository, gitHubRepository, sshPassed)`; P7, P8, P9 | +| `FrameworkInstaller.install(project, enablePlanGate) : InstallResult` | [SD-001] `install(localProject, enablePlanGate)`; P10, P11, P12 | +| `SummaryReport.compose(request) : Summary` | [SD-001] `compose(projectRequest)`; P13 | + +## Pattern Annotations + +| Pattern | Classes | Rationale | +| --- | --- | --- | +| Controller (GRASP) | `ProjectCreator` | One entry for the system operations; coordinates and does no HTTP, git or file work itself | +| Facade (GoF) | `GitHost`, `GiteaClient`, `GitHubClient` | Each client hides one host's HTTP API and keeps the token inside; no other class sees a credential. `GitHost` holds the operations both share | +| Pure Fabrication (GRASP) | `ConfigLoader`, `ToolChecker`, `Preflight`, `LocalProjectBuilder`, `FrameworkInstaller`, `SummaryReport` | No domain concept owns these responsibilities; small units keep cohesion high | +| Creator (GRASP) | `ConfigLoader` creates `Configuration`; `GiteaClient` creates `GiteaRepository` and `PushMirror` | The creating class holds the data needed to build the object | +| Protection from variations (GRASP) | `GiteaClient`, `GitHubClient`, `ProjectRequest` | The optional GitHub path is decided by the controller; the clients do not know it | +| Data Transfer Object (GoF-style) | `InstallResult` | Carries the three results of `install` in one return value | + +## Dependency Check + +No circular dependency. `ProjectCreator` depends on every helper class and no helper depends on it. `Preflight` depends on the two clients; the clients extend `GitHost` and depend only on `Configuration`. The data classes form a tree: `Run` holds `Configuration`, `ToolCheck` and `ProjectRequest`; `ProjectRequest` reaches the repositories and the `LocalProject`; `Summary` points at `ProjectRequest` and nothing points back at it. `Repository` is shared by `Remote` and `PushMirror` without a cycle. + +SOLID check: no class has more than one reason to change (one host API, one kind of local work, one report); the clients can be replaced behind the same operations; the controller depends on the operations, not on how a host or git is called. `ProjectCreator` has two operations and no data, so it is not a god class. + +## Implementation Mapping + +| Design class | Where it lives in `src/` | +| --- | --- | +| `ProjectCreator` | `create-project.sh` (`main`), `lib/apply.sh` | +| `ConfigLoader` | `lib/config.sh` (`load_configuration`), `lib/validate.sh` | +| `ToolChecker` | `lib/tools.sh` | +| `Preflight` | `lib/preflight.sh` | +| `GitHost`, `GiteaClient`, `GitHubClient` | `lib/api.sh`, `lib/http.sh`, `lib/json.sh`, `lib/repositories.sh`, `lib/mirror.sh`, `lib/hosts.sh` | +| `LocalProjectBuilder` | `lib/localproject.sh`, `lib/git.sh` | +| `FrameworkInstaller` | `lib/framework.sh` | +| `SummaryReport` | `lib/steps.sh`, `lib/plan.sh` | +| `PromptSet`, `ProjectRequest` | `lib/project.sh`, `lib/prompts.sh` | +| `Run`, `Configuration`, `Credential`, `PreflightResult` | the state arrays declared in `lib/constants.sh` | + +--- + +[DCD-001]: ./uc-001/dcd.md +[DM-002]: ./domain-model.md +[UC-001]: ./uc-001/uc.md +[OC-001]: ./uc-001/oc.md +[SD-001]: ./uc-001/sd.md +[DICT-001]: ./dictionary.md diff --git a/docs/dictionary.md b/docs/dictionary.md index 0d98d05..854dc92 100644 --- a/docs/dictionary.md +++ b/docs/dictionary.md @@ -10,6 +10,7 @@ | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | | 2026-10-05 | Accepted | Jens Tirsvad Nielsen | S02 | Initial version, terms of UC-001 | [02875ae] | +| 2026-10-06 | Proposed | Jens Tirsvad Nielsen | S02 | The IT terms are also used in DCD-001 and DCD-002
InstallResult and Visibility named as design-only types | pending | --- @@ -21,23 +22,23 @@ Maps each Product Owner (PO) term to its professional IT term. PO language: Engl | PO term | Language | IT term | Definition | Used as PO term in | Used as IT term in | | --- | --- | --- | --- | --- | --- | -| Maintainer | en | Maintainer | The person who creates a new project. | DM, UC, US | OC, SD | -| Project | en | ProjectRequest | The new software project being set up, with its name, description and visibility. | DM, UC, US | OC, SD | -| Configuration | en | Configuration | The service addresses and access tokens set up before starting. | DM, UC | OC, SD | -| Git Host | en | GitHost | A service that holds repositories: Gitea or GitHub. | DM, UC | OC, SD | -| Access Token | en | Credential | A secret that lets the Maintainer act on a Git Host; never part of an address. | DM, UC | OC, SD | -| Owner | en | Owner | The user or organization on a Git Host that owns repositories. | DM, UC | OC, SD | -| Repository | en | Repository | A place on a Git Host that holds a project's history. | DM, UC | OC, SD | -| Gitea Repository | en | GiteaRepository | The repository on Gitea; the source of truth. | DM, UC | OC, SD | -| GitHub Repository | en | GitHubRepository | The repository on GitHub; it receives its content from the mirror. | DM, UC | OC, SD | -| License | en | LicenseFile | The legal terms file (AGPL-3.0) added to the Gitea repository when GitHub is chosen. | DM, UC | OC, SD | -| Mirror | en | PushMirror | The push mirror that copies a Gitea repository to a GitHub repository. | DM, UC | OC, SD | -| Local Project | en | LocalProject | The project directory on the Maintainer's machine. | DM, UC | OC, SD | -| Remote | en | Remote | A named link from a local project to a repository. | DM, UC | OC, SD | -| Framework | en | Submodule | The SQA-QC-Framework added to a local project; the IT term names how it is attached. | DM, UC | OC, SD | -| Framework Setup | en | HookSetup | The skills and git hooks installed from the framework, with the plan gate on or off. | DM, UC | OC, SD | -| Template | en | Template | A framework file copied into a project. | DM, UC | OC, SD | -| Summary | en | Summary | The report of what was created, skipped or failed and how to continue. | DM, UC | OC, SD | +| Maintainer | en | Maintainer | The person who creates a new project. | DM, UC, US | OC, SD, DCD | +| Project | en | ProjectRequest | The new software project being set up, with its name, description and visibility. | DM, UC, US | OC, SD, DCD | +| Configuration | en | Configuration | The service addresses and access tokens set up before starting. | DM, UC | OC, SD, DCD | +| Git Host | en | GitHost | A service that holds repositories: Gitea or GitHub. | DM, UC | OC, SD, DCD | +| Access Token | en | Credential | A secret that lets the Maintainer act on a Git Host; never part of an address. | DM, UC | OC, SD, DCD | +| Owner | en | Owner | The user or organization on a Git Host that owns repositories. | DM, UC | OC, SD, DCD | +| Repository | en | Repository | A place on a Git Host that holds a project's history. | DM, UC | OC, SD, DCD | +| Gitea Repository | en | GiteaRepository | The repository on Gitea; the source of truth. | DM, UC | OC, SD, DCD | +| GitHub Repository | en | GitHubRepository | The repository on GitHub; it receives its content from the mirror. | DM, UC | OC, SD, DCD | +| License | en | LicenseFile | The legal terms file (AGPL-3.0) added to the Gitea repository when GitHub is chosen. | DM, UC | OC, SD, DCD | +| Mirror | en | PushMirror | The push mirror that copies a Gitea repository to a GitHub repository. | DM, UC | OC, SD, DCD | +| Local Project | en | LocalProject | The project directory on the Maintainer's machine. | DM, UC | OC, SD, DCD | +| Remote | en | Remote | A named link from a local project to a repository. | DM, UC | OC, SD, DCD | +| Framework | en | Submodule | The SQA-QC-Framework added to a local project; the IT term names how it is attached. | DM, UC | OC, SD, DCD | +| Framework Setup | en | HookSetup | The skills and git hooks installed from the framework, with the plan gate on or off. | DM, UC | OC, SD, DCD | +| Template | en | Template | A framework file copied into a project. | DM, UC | OC, SD, DCD | +| Summary | en | Summary | The report of what was created, skipped or failed and how to continue. | DM, UC | OC, SD, DCD | ## Rules @@ -47,6 +48,10 @@ Maps each Product Owner (PO) term to its professional IT term. PO language: Engl - `Run`, `ToolCheck`, `PreflightResult` and `PromptSet` appear in [OC-001] but have no PO term: they are system concepts, not domain concepts, and are not in the Domain Model. +- `InstallResult` and the enumeration `Visibility` appear only in [DCD-001]: + `InstallResult` carries the three results of one operation, and `Visibility` + is the type of the Project and Repository attribute of the same name. Neither + is a domain concept. - A new concept in a Domain Model gets a row here in the same change. --- @@ -56,4 +61,5 @@ Maps each Product Owner (PO) term to its professional IT term. PO language: Engl [DM-001]: ./uc-001/dm.md [DM-002]: ./domain-model.md [OC-001]: ./uc-001/oc.md +[DCD-001]: ./uc-001/dcd.md [02875ae]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/02875aee5f2953473924074eea0056eb31af6b7a diff --git a/docs/sqa/traceability-matrix.md b/docs/sqa/traceability-matrix.md index d1fa54c..06734df 100644 --- a/docs/sqa/traceability-matrix.md +++ b/docs/sqa/traceability-matrix.md @@ -9,8 +9,8 @@ ## Version History | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | -| 2026-10-05 | Proposed | Jens Tirsvad Nielsen | S02 | Initial version, UC-001 artifacts and baseline | [02875ae] | | 2026-10-05 | Proposed | Jens Tirsvad Nielsen | S02 | Added MIL-004 and RC-018 | [2a6bb8e] | +| 2026-10-06 | Proposed | Jens Tirsvad Nielsen | S02 | Added DCD-001 and DCD-002 | pending | --- @@ -35,16 +35,18 @@ updated whenever an artifact instance is created or reviewed. | [US-001] | US | [BC-001], [UCD-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004] | [UC-001] | [RC-001] | | [UC-001] | UC | [UCD-001], [US-001], [SA-001] | [SSD-001], [DM-001] | [RC-002] | | [SSD-001] | SSD | [UC-001] | [OC-001] | [RC-003] | -| [DM-001] | DM | [UC-001], [SSD-001] | [DM-002], [DICT-001], [OC-001] | [RC-004] | -| [DM-002] | DM | [DM-001] | [DICT-001] | [RC-005] | +| [DM-001] | DM | [UC-001], [SSD-001] | [DM-002], [DICT-001], [OC-001], [DCD-001] | [RC-004] | +| [DM-002] | DM | [DM-001] | [DICT-001], [DCD-001], [DCD-002] | [RC-005] | | [DICT-001] | DICT | [BC-001], [SA-001], [DM-001], [DM-002] | [OC-001], [SD-001] | [RC-008] | | [OC-001] | OC | [SSD-001], [DM-001] | [SD-001] | [RC-006] | -| [SD-001] | SD | [OC-001] | - | [RC-007] | +| [SD-001] | SD | [OC-001] | [DCD-001] | [RC-007] | +| [DCD-001] | DCD | [UC-001], [DM-001], [DM-002], [OC-001], [SD-001], [DICT-001] | [DCD-002] | - | +| [DCD-002] | DCD | [DCD-001], [DM-002], [DICT-001] | - | - | ## Coverage Notes - Reviewed so far: every artifact in the project (see the Last Reviewed column). -- No Design Class Diagram, ERD, KPI, BMC or BPMN exists yet. `-` in Downstream means nothing is built on the artifact yet. +- [DCD-001] and [DCD-002] are proposed and not reviewed yet. No ERD, KPI, BMC or BPMN exists yet. `-` in Downstream means nothing is built on the artifact yet. --- @@ -57,6 +59,8 @@ updated whenever an artifact instance is created or reviewed. [MIL-004]: ../milestones/mil-004-configurable-details.md [RC-018]: ./reviews/rc-018-mil-004.md [RC-019]: ./reviews/rc-019-mil-004-code.md +[DCD-001]: ../uc-001/dcd.md +[DCD-002]: ../dcd.md [UCD-001]: ../use-case-diagram.md [US-001]: ../user-stories.md [UC-001]: ../uc-001/uc.md @@ -83,5 +87,4 @@ updated whenever an artifact instance is created or reviewed. [RC-015]: ./reviews/rc-015-mil-003.md [RC-016]: ./reviews/rc-016-create-project-sh.md [RC-017]: ./reviews/rc-017-e2e-security-review.md -[02875ae]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/02875aee5f2953473924074eea0056eb31af6b7a [2a6bb8e]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/2a6bb8e8afadfe6ca4a621da30e44a372898ca62 diff --git a/docs/uc-001/dcd.md b/docs/uc-001/dcd.md new file mode 100644 index 0000000..65e5b2c --- /dev/null +++ b/docs/uc-001/dcd.md @@ -0,0 +1,315 @@ +# Design Class Diagram (UC-001) + +## Metadata +| Key | Value | +| --- | --- | +| ID | DCD-001 | +| CrossReference | [UC-001], [DM-001], [DM-002], [OC-001], [SD-001], [DICT-001] | + +## Version History +| Date | Status | Author | Reviewer | Change | Commit | +| --- | --- | --- | --- | --- | --- | +| 2026-10-06 | Proposed | Jens Tirsvad Nielsen | S02 | Initial version | pending | + +--- + +## Purpose and Scope + +Covers [UC-001] "Create a new project". It refines the concepts of [DM-001] into design classes and turns the messages of [SD-001] into method signatures, so that every method traces to a contract in [OC-001] or to a message in [SD-001]. Class and attribute names are the IT terms of [DICT-001]. The project-level model that consolidates all use cases is [DCD-002]. + +The classes are design classes of a Bash program: a class is a group of functions in `src/lib/` with its data held in the shared state arrays (see "Implementation Mapping"). There is no object-oriented runtime, but the responsibilities, associations and dependencies below are the ones the code keeps. + +Failure handling (the exceptions of [OC-001]) is one rule of `ProjectCreator`, stop and report, and is not drawn. + +## Diagram + +```plantuml +@startuml +skinparam classAttributeIconSize 0 +hide empty members + +enum Visibility { + private + public +} + +class ProjectCreator <> { + +startProjectCreation() : PromptSet + +provideProjectDetails(name : String, description : String, visibility : Visibility, giteaOwner : Owner, githubOwner : Owner [0..1], directory : Path, enablePlanGate : Boolean) : Summary +} +class ConfigLoader { + +load(configFile : Path, envFile : Path) : Configuration +} +class ToolChecker { + +check(tools : String [1..*]) : ToolCheck +} +class Preflight { + +check(request : ProjectRequest) : PreflightResult +} +abstract class GitHost <> { + -name : String + -webAddress : String + -apiAddress : String + +verifyToken() : Boolean + +ownerAccepts(owner : Owner) : Boolean + +nameFree(name : String) : Boolean +} +class GiteaClient <> { + +GiteaClient(configuration : Configuration) + +hasLicense(key : String) : Boolean + +createRepository(request : ProjectRequest, license : String [0..1]) : GiteaRepository + +addPushMirror(source : GiteaRepository, target : GitHubRepository) : PushMirror + -requestSync(mirror : PushMirror) : void +} +class GitHubClient <> { + +GitHubClient(configuration : Configuration) + +createEmptyRepository(request : ProjectRequest) : GitHubRepository +} +class LocalProjectBuilder { + +build(directory : Path, source : GiteaRepository, target : GitHubRepository [0..1], sshPassed : Boolean) : LocalProject +} +class FrameworkInstaller { + +install(project : LocalProject, enablePlanGate : Boolean) : InstallResult +} +class SummaryReport { + +compose(request : ProjectRequest) : Summary +} + +class Run { + -isApply : Boolean +} +class Configuration { + -giteaUrl : String + -giteaApiUrl : String + -githubWebUrl : String + -githubApiUrl : String + -giteaSshPort : Integer + -mirrorInterval : String + -frameworkRepo : String + -presetDetails : Map [0..1] +} +class Credential { + -kind : String + -value : String +} +class ToolCheck { + -hasGit : Boolean + -hasCurl : Boolean + -hasJq : Boolean +} +class PromptSet { + -prompts : String [1..*] +} +class ProjectRequest { + -name : String + -description : String + -visibility : Visibility + -directory : Path + -enablePlanGate : Boolean +} +class Owner { + -name : String + -kind : String +} +class PreflightResult { + -tokensWork : Boolean + -ownersAccept : Boolean + -nameIsFree : Boolean + -licenseIsOffered : Boolean + -sshPassed : Boolean +} +abstract class Repository { + -name : String + -description : String + -visibility : Visibility + -address : String +} +class GiteaRepository +class GitHubRepository +class LicenseFile { + -key : String +} +class PushMirror { + -interval : String + -syncOnCommit : Boolean +} +class LocalProject { + -directory : Path +} +class Remote { + -name : String + -address : String +} +class Submodule { + -name : String + -address : String +} +class HookSetup { + -areSkillsInstalled : Boolean + -areHooksInstalled : Boolean + -isPlanGateEnabled : Boolean +} +class Template { + -name : String + -isCopied : Boolean +} +class InstallResult <> +class Summary { + -createdItems : String [0..*] + -skippedItems : String [0..*] + -nextSteps : String [0..*] +} + +ProjectCreator ..> ConfigLoader : creates +ProjectCreator ..> ToolChecker : creates +ProjectCreator ..> Preflight : creates +ProjectCreator ..> GiteaClient : creates +ProjectCreator ..> GitHubClient : creates [0..1] +ProjectCreator ..> LocalProjectBuilder : creates +ProjectCreator ..> FrameworkInstaller : creates +ProjectCreator ..> SummaryReport : creates +Preflight ..> GiteaClient : asks +Preflight ..> GitHubClient : asks [0..1] +GitHost <|-- GiteaClient +GitHost <|-- GitHubClient +GitHost "1" --> "0..*" Owner : has +GiteaClient ..> Configuration +GitHubClient ..> Configuration + +ProjectCreator --> "1" Run +Run *-- "1" Configuration +Run *-- "1" ToolCheck +Run *-- "0..1" ProjectRequest +Run --> "1" PromptSet : returns +Configuration *-- "1..2" Credential + +ProjectRequest --> "1" Owner : giteaOwner +ProjectRequest --> "0..1" Owner : githubOwner +ProjectRequest *-- "0..1" PreflightResult +ProjectRequest --> "0..1" GiteaRepository : stored in +ProjectRequest --> "0..1" GitHubRepository : also stored in +ProjectRequest --> "0..1" LocalProject : working copy +Summary --> "1" ProjectRequest : reports on + +Repository <|-- GiteaRepository +Repository <|-- GitHubRepository +Repository --> "1" Owner : owned by +GiteaRepository *-- "0..1" LicenseFile +PushMirror --> "1" GiteaRepository : source +PushMirror --> "1" GitHubRepository : target +PushMirror --> "1" Credential : authorised by + +LocalProject *-- "1..2" Remote +Remote --> "1" Repository : points to +LocalProject *-- "1" Submodule +LocalProject *-- "1" HookSetup +LocalProject *-- "0..*" Template +InstallResult --> "1" Submodule +InstallResult --> "1" HookSetup +InstallResult --> "0..*" Template + +ProjectRequest --> Visibility +Repository --> Visibility +@enduml +``` + +## Class Table + +| Class | Refines (Domain Model concept) | Responsibility | Attributes | Operations | +| --- | --- | --- | --- | --- | +| `ProjectCreator` | none (controller for the system operations of [OC-001]) | Receives the two system operations, sequences the steps and stops on the first failure. | none | `startProjectCreation`, `provideProjectDetails` | +| `ConfigLoader` | Configuration | Reads `config.env` and `.env` as plain text and validates every value, preset project details included. | none | `load` | +| `ToolChecker` | none (system concept `ToolCheck`) | Detects the required and optional tools. | none | `check` | +| `Preflight` | none (system concept `PreflightResult`) | Runs the read-only checks of both hosts before anything is created. | none | `check` | +| `GitHost` | Git Host | The operations every host offers: check the token, check that an owner accepts new repositories, check that a name is free. | `name`, `webAddress`, `apiAddress` | `verifyToken`, `ownerAccepts`, `nameFree` | +| `GiteaClient` | Git Host (Gitea) | Hides the Gitea API and its token; creates the repository and the push mirror. | none beyond `GitHost` (uses `Configuration`) | `GiteaClient`, `hasLicense`, `createRepository`, `addPushMirror`, `requestSync` | +| `GitHubClient` | Git Host (GitHub) | Hides the GitHub API and its token; creates the empty repository. | none beyond `GitHost` (uses `Configuration`) | `GitHubClient`, `createEmptyRepository` | +| `LocalProjectBuilder` | Local Project, Remote | Creates the project directory, its git repository and its credential-free remotes. | none | `build` | +| `FrameworkInstaller` | Framework, Framework Setup, Template | Adds the framework submodule, installs skills and hooks once, and copies the templates without overwriting. | none | `install` | +| `SummaryReport` | Summary | Composes the report of what was created, skipped or failed. | none | `compose` | +| `Run` | none (system concept) | Holds the state of one execution. | `isApply` | none | +| `Configuration` | Configuration | Holds the service addresses, the credentials and any preset project details. | `giteaUrl`, `giteaApiUrl`, `githubWebUrl`, `githubApiUrl`, `giteaSshPort`, `mirrorInterval`, `frameworkRepo`, `presetDetails` | none | +| `Credential` | Access Token | Holds a secret in memory only; it never becomes part of an address or a message. | `kind`, `value` | none | +| `ToolCheck` | none (system concept) | Records which tools are present. | `hasGit`, `hasCurl`, `hasJq` | none | +| `PromptSet` | none (system concept) | The questions still to ask; a detail preset in `config.env` is not in it. | `prompts` | none | +| `ProjectRequest` | Project | Holds the details of the project being created. | `name`, `description`, `visibility`, `directory`, `enablePlanGate` | none | +| `Owner` | Owner | A user or organization on a host. | `name`, `kind` | none | +| `PreflightResult` | none (system concept) | Records the outcome of the preflight checks. | `tokensWork`, `ownersAccept`, `nameIsFree`, `licenseIsOffered`, `sshPassed` | none | +| `Repository` | Repository | Common data of a repository on a host. | `name`, `description`, `visibility`, `address` | none | +| `GiteaRepository` | Gitea Repository | The source of truth. | none beyond `Repository` | none | +| `GitHubRepository` | GitHub Repository | Receives its content from the mirror. | none beyond `Repository` | none | +| `LicenseFile` | License | The `AGPL-3.0` file in the Gitea repository when GitHub is chosen. | `key` | none | +| `PushMirror` | Mirror | The Gitea to GitHub push mirror. | `interval`, `syncOnCommit` | none | +| `LocalProject` | Local Project | The project directory on the Maintainer's machine. | `directory` | none | +| `Remote` | Remote | A named link to a repository (`origin`, `github`), without a credential. | `name`, `address` | none | +| `Submodule` | Framework | The framework added to the local project. | `name`, `address` | none | +| `HookSetup` | Framework Setup | Records the skills and hooks installed and the plan gate state. | `areSkillsInstalled`, `areHooksInstalled`, `isPlanGateEnabled` | none | +| `Template` | Template | A framework file copied into the project. | `name`, `isCopied` | none | +| `InstallResult` | none (carries the result of one operation) | Returns the submodule, the hook setup and the templates of `install`. | none | none | +| `Summary` | Summary | The report returned to the Maintainer; it contains no credential. | `createdItems`, `skippedItems`, `nextSteps` | none | +| `Visibility` | none (enumeration of a Project and Repository attribute) | The two allowed visibilities. | `private`, `public` | none | + +## Method Traceability + +| Method signature | Operation Contract / SD message | +| --- | --- | +| `ProjectCreator.startProjectCreation() : PromptSet` | [OC-001] `startProjectCreation`; [SD-001] `startProjectCreation()` | +| `ProjectCreator.provideProjectDetails(name, description, visibility, giteaOwner, githubOwner, directory, enablePlanGate) : Summary` | [OC-001] `provideProjectDetails`; [SD-001] `provideProjectDetails(...)` | +| `ConfigLoader.load(configFile, envFile) : Configuration` | [SD-001] `load(config.env, .env)`; [OC-001] `startProjectCreation` P2 | +| `ToolChecker.check(tools) : ToolCheck` | [SD-001] `check(git, curl, jq)`; [OC-001] `startProjectCreation` P3 | +| `Preflight.check(request) : PreflightResult` | [SD-001] `check(request)`; [OC-001] `provideProjectDetails` P2 | +| `GiteaClient(configuration)` | [SD-001] `new(configuration)` to `GiteaClient` | +| `GitHost.verifyToken() : Boolean` | [SD-001] `verifyToken()` from `Preflight` to either client; P2 | +| `GitHost.ownerAccepts(owner) : Boolean` | [SD-001] `ownerAccepts(giteaOwner)` and `ownerAccepts(githubOwner)`; P2 | +| `GitHost.nameFree(name) : Boolean` | [SD-001] `nameFree(name)` to either client; P2 | +| `GiteaClient.hasLicense(key) : Boolean` | [SD-001] `hasLicense(AGPL-3.0)`; P2 | +| `GiteaClient.createRepository(request, license) : GiteaRepository` | [SD-001] `createRepository(request, license)`; P3, P4 | +| `GiteaClient.addPushMirror(source, target) : PushMirror` | [SD-001] `addPushMirror(giteaRepository, gitHubRepository)`; P6 | +| `GiteaClient.requestSync(mirror) : void` | [SD-001] `requestSync(pushMirror)`; P6 | +| `GitHubClient(configuration)` | [SD-001] `new(configuration)` to `GitHubClient` | +| `GitHubClient.createEmptyRepository(request) : GitHubRepository` | [SD-001] `createEmptyRepository(request)`; P5 | +| `LocalProjectBuilder.build(directory, source, target, sshPassed) : LocalProject` | [SD-001] `build(directory, giteaRepository, gitHubRepository, sshPassed)`; P7, P8, P9 | +| `FrameworkInstaller.install(project, enablePlanGate) : InstallResult` | [SD-001] `install(localProject, enablePlanGate)`; P10, P11, P12 | +| `SummaryReport.compose(request) : Summary` | [SD-001] `compose(projectRequest)`; P13 | + +## Pattern Annotations + +| Pattern | Classes | Rationale | +| --- | --- | --- | +| Controller (GRASP) | `ProjectCreator` | One entry for the system operations; coordinates and does no HTTP, git or file work itself | +| Facade (GoF) | `GitHost`, `GiteaClient`, `GitHubClient` | Each client hides one host's HTTP API and keeps the token inside; no other class sees a credential. `GitHost` holds the operations both share | +| Pure Fabrication (GRASP) | `ConfigLoader`, `ToolChecker`, `Preflight`, `LocalProjectBuilder`, `FrameworkInstaller`, `SummaryReport` | No domain concept owns these responsibilities; small units keep cohesion high | +| Creator (GRASP) | `ConfigLoader` creates `Configuration`; `GiteaClient` creates `GiteaRepository` and `PushMirror` | The creating class holds the data needed to build the object | +| Protection from variations (GRASP) | `GiteaClient`, `GitHubClient`, `ProjectRequest` | The optional GitHub path is decided by the controller; the clients do not know it | +| Data Transfer Object (GoF-style) | `InstallResult` | Carries the three results of `install` in one return value | + +## Dependency Check + +No circular dependency. `ProjectCreator` depends on every helper class and no helper depends on it. `Preflight` depends on the two clients; the clients extend `GitHost` and depend only on `Configuration`. The data classes form a tree: `Run` holds `Configuration`, `ToolCheck` and `ProjectRequest`; `ProjectRequest` reaches the repositories and the `LocalProject`; `Summary` points at `ProjectRequest` and nothing points back at it. `Repository` is shared by `Remote` and `PushMirror` without a cycle. + +SOLID check: no class has more than one reason to change (one host API, one kind of local work, one report); the clients can be replaced behind the same operations; the controller depends on the operations, not on how a host or git is called. `ProjectCreator` has two operations and no data, so it is not a god class. + +## Implementation Mapping + +| Design class | Where it lives in `src/` | +| --- | --- | +| `ProjectCreator` | `create-project.sh` (`main`), `lib/apply.sh` | +| `ConfigLoader` | `lib/config.sh` (`load_configuration`), `lib/validate.sh` | +| `ToolChecker` | `lib/tools.sh` | +| `Preflight` | `lib/preflight.sh` | +| `GitHost`, `GiteaClient`, `GitHubClient` | `lib/api.sh`, `lib/http.sh`, `lib/json.sh`, `lib/repositories.sh`, `lib/mirror.sh`, `lib/hosts.sh` | +| `LocalProjectBuilder` | `lib/localproject.sh`, `lib/git.sh` | +| `FrameworkInstaller` | `lib/framework.sh` | +| `SummaryReport` | `lib/steps.sh`, `lib/plan.sh` | +| `PromptSet`, `ProjectRequest` | `lib/project.sh`, `lib/prompts.sh` | +| `Run`, `Configuration`, `Credential`, `PreflightResult` | the state arrays declared in `lib/constants.sh` | + +--- + +[UC-001]: ./uc.md +[DM-001]: ./dm.md +[DM-002]: ../domain-model.md +[OC-001]: ./oc.md +[SD-001]: ./sd.md +[DICT-001]: ../dictionary.md +[DCD-002]: ../dcd.md diff --git a/docs/uc-001/sd.md b/docs/uc-001/sd.md index eb0f9c4..be8a69c 100644 --- a/docs/uc-001/sd.md +++ b/docs/uc-001/sd.md @@ -4,17 +4,17 @@ | Key | Value | | --- | --- | | ID | SD-001 | -| CrossReference | [OC-001] | +| CrossReference | [OC-001], [DCD-001] | ## Version History | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | -| 2026-10-05 | Accepted | Jens Tirsvad Nielsen | S02 | Initial version | [02875ae] | | 2026-10-05 | Accepted | Jens Tirsvad Nielsen | S02 | Note: preset project details are read by ConfigLoader | [2a6bb8e] | +| 2026-10-06 | Proposed | Jens Tirsvad Nielsen | S02 | Messages aligned with the method signatures of DCD-001
Cited DCD-001 | pending | --- -Design objects are conceptual; in `create-project.sh` each becomes a small function group. No Design Class Diagram exists yet. +Design objects are conceptual; in `create-project.sh` each becomes a small function group. [DCD-001] gives each object its class and turns each message below into a method signature. ## Sequence: startProjectCreation @@ -77,7 +77,7 @@ destroy TC ```plantuml @startuml -actor Maintainer +'actor Maintainer participant ":ProjectCreator" as PC participant ":Preflight" as PF participant ":GiteaClient" as GT @@ -86,7 +86,7 @@ participant ":LocalProjectBuilder" as LB participant ":FrameworkInstaller" as FI participant ":SummaryReport" as SR -Maintainer -> PC : provideProjectDetails(name, description, visibility, giteaOwner, githubOwner, directory, enablePlanGate) + -> PC : provideProjectDetails(name, description, visibility, giteaOwner, githubOwner, directory, enablePlanGate) activate PC create GT PC -> GT : new(configuration) @@ -105,16 +105,16 @@ PF --> PC : preflightResult deactivate PF opt githubOwner present - PC -> GH : createEmptyRepository(githubOwner, name) + PC -> GH : createEmptyRepository(request) activate GH GH --> PC : gitHubRepository deactivate GH end alt githubOwner present - PC -> GT : createRepository(giteaOwner, name, license=AGPL-3.0) + PC -> GT : createRepository(request, license=AGPL-3.0) else no GitHub - PC -> GT : createRepository(giteaOwner, name, license=none) + PC -> GT : createRepository(request, license=none) end activate GT GT --> PC : giteaRepository @@ -123,7 +123,7 @@ deactivate GT opt githubOwner present PC -> GT : addPushMirror(giteaRepository, gitHubRepository) activate GT - GT -> GT : requestSync() + GT -> GT : requestSync(pushMirror) GT --> PC : pushMirror deactivate GT end @@ -141,9 +141,8 @@ FI --> PC : submodule, hookSetup, templates deactivate FI create SR -PC -> SR : compose(all results) +PC -> SR : compose(request) SR --> PC : summary -PC --> Maintainer : summary deactivate PC destroy PF destroy GT @@ -169,17 +168,17 @@ destroy SR | --- | --- | | P1 ProjectRequest created | `provideProjectDetails` received by `ProjectCreator` | | P2 PreflightResult created | `check(request)` | -| P3 GiteaRepository created | `createRepository(giteaOwner, name, license)` | +| P3 GiteaRepository created | `createRepository(request, license)` | | P4 LicenseFile when GitHub chosen, otherwise empty | `createRepository(..., license=AGPL-3.0)` and the `alt` branch `license=none` | -| P5 empty GitHubRepository when chosen | `createEmptyRepository(githubOwner, name)` | -| P6 PushMirror and first sync | `addPushMirror(...)` and `requestSync()` | +| P5 empty GitHubRepository when chosen | `createEmptyRepository(request)` | +| P6 PushMirror and first sync | `addPushMirror(...)` and `requestSync(pushMirror)` | | P7 LocalProject created, history from Gitea when not empty | `build(directory, ...)` | | P8 origin remote (SSH if the test passed, else HTTPS) | `build(..., sshPassed)` | | P9 github remote when chosen | `build(...)` | | P10 framework Submodule | `install(localProject, ...)` | | P11 HookSetup, plan gate if chosen | `install(localProject, enablePlanGate)` | | P12 AGENTS.md and registry copied or kept | `install(...)` returning `templates` | -| P13 Summary created and returned | `compose(all results)` and the final return | +| P13 Summary created and returned | `compose(request)` and the final return | ### Responsibility Check @@ -188,5 +187,5 @@ destroy SR --- [OC-001]: ./oc.md -[02875ae]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/02875aee5f2953473924074eea0056eb31af6b7a +[DCD-001]: ./dcd.md [2a6bb8e]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/2a6bb8e8afadfe6ca4a621da30e44a372898ca62