Author SHA1 Message Date
TirsvadandClaude Sonnet 5.5 7d202a34dc Resolve pending commit links for the MIL-005 plan
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-06 12:47:33 +08:00
TirsvadandClaude Sonnet 5.5 ded26a658c Plan MIL-005: ask for missing credentials and create the project .env
Add objective 9 to the Business Case (and amend objective 6 and success
criterion 1: a token may be written only to the new project's .env after a
yes), US-001.05, UC-001 extensions 2b, 9c and 9d, with the SSD, OC, SD,
DM-001, DM-002 and dictionary in step. Add the classes CredentialCollector,
EnvFileWriter and EnvFile to DCD-001 and DCD-002, and both ends'
multiplicities to every association. Restore three lines of SD-001 damaged
by an earlier edit.

Add milestone MIL-005 (9 Go/No-Go criteria, 5 tasks) and its phase in the
Project Plan. Accept the planning set and the two DCDs; reviews recorded in
RC-020 and RC-021.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-06 12:47:26 +08:00
TirsvadandClaude Sonnet 5.5 4c9a1af719 Resolve pending commit links for the DCDs
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-06 12:35:47 +08:00
TirsvadandClaude Sonnet 5.5 f4d611b77c 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 <noreply@anthropic.com>
2026-10-06 12:35:45 +08:00
Tirsvad cf265e8f66 Merge pull request 'MIL-004: project details preset in config.env (with the e2e fixes and the plan)' (#34) from mil-004-configurable-details into main
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 5s
Reviewed-on: #34
2026-10-05 18:28:47 +02:00
TirsvadandClaude Sonnet 5.5 55d9ca6eee Add a section banner to .env.example
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-06 00:27:00 +08:00
TirsvadandClaude Sonnet 5.5 6b1b9c6af4 Ignore config.env
The file holds the Maintainer's own addresses and project details, so it
stays out of the repository like .env.

Refs #31

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-06 00:25:48 +08:00
TirsvadandClaude Sonnet 5.5 4634048eab Resolve pending commit link for RC-019
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-05 23:18:30 +08:00
TirsvadandClaude Sonnet 5.5 ceaa7d18d8 Code review of MIL-004: document quoting of values, add RC-019
- Say in the README and config.env.example that a value containing " #"
  must be quoted, and pin both behaviours with a test.
- Say in the script header and the README that details set in config.env
  are not asked.
- Record the review as RC-019 and link it in the traceability matrix.

Task: MIL-004#4
Task: MIL-004#5
Refs #30
Refs #31

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-05 23:18:29 +08:00
TirsvadandClaude Sonnet 5.5 75e8e91f9a Let config.env preset the project details
Eight optional keys (PROJECT_NAME, PROJECT_DESCRIPTION, PROJECT_VISIBILITY,
GITEA_OWNER, USE_GITHUB, GITHUB_OWNER, PROJECT_DIRECTORY, ENABLE_PLAN_GATE)
are read and checked with the validators the prompts use. A key that is
present counts as set (only the description may be empty); it is not asked
and the summary marks it "(from config.env)". An invalid value stops the
run before any request and names the key. USE_GITHUB=no skips the GitHub
owner and warns about a stray GITHUB_OWNER. The confirmations stay
interactive. Keys and an example are documented; tests cover every key.

Task: MIL-004#1
Task: MIL-004#2
Task: MIL-004#3
Task: MIL-004#4
Task: MIL-004#5
Closes #27
Closes #28
Closes #29
Closes #30
Closes #31

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-05 22:06:45 +08:00
Tirsvad c21a3c50f0 Merge pull request 'MIL-003: local project, framework submodule, hooks, templates and README' (#26) from mil-003-scaffold-and-release into main
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 4s
Reviewed-on: #26
2026-10-05 09:55:13 +02:00
Tirsvad 5584ddf397 Merge pull request 'MIL-002: GitHub and Gitea steps (dry run, repositories, push mirror) and split into library files' (#25) from mil-002-repositories-and-mirror into main
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 4s
Reviewed-on: #25
2026-10-05 09:33:06 +02:00
27 changed files with 1621 additions and 133 deletions
+4
View File
@@ -12,6 +12,10 @@
# chosen owner and to push to the new one. Prefer a fine-grained token. # chosen owner and to push to the new one. Prefer a fine-grained token.
GITHUB_PAT= GITHUB_PAT=
########################################
# Secrets for workframe
########################################
# GitHub account the token belongs to. It identifies who authenticates; it is # GitHub account the token belongs to. It identifies who authenticates; it is
# only a default suggestion for the owner prompt, because the repository can # only a default suggestion for the owner prompt, because the repository can
# belong to an organization. # belong to an organization.
+1
View File
@@ -187,3 +187,4 @@ repofoundry.*/
!src/lib !src/lib
config.env
+41 -2
View File
@@ -1,4 +1,4 @@
# RepoFoundry # Repo Foundry
RepoFoundry (`src/create-project.sh`) sets up a new project in one run: RepoFoundry (`src/create-project.sh`) sets up a new project in one run:
@@ -87,6 +87,44 @@ chmod 600 .env # Linux and macOS
Every URL must start with `https://` and must not contain a user name, Every URL must start with `https://` and must not contain a user name,
password, query string or fragment. A credential key in this file is rejected. password, query string or fragment. A credential key in this file is rejected.
### `config.env` (project details, optional)
Any of the details the script asks for can be set in `config.env` instead.
A detail that is set is used and not asked; the summary marks it with
`(from config.env)`.
| Key | Detail | Accepted value |
| --- | --- | --- |
| `PROJECT_NAME` | repository name | letters, digits, `.`, `_`, `-`; at most 100; not ending in `.git` |
| `PROJECT_DESCRIPTION` | description | at most 350 characters; may be empty |
| `PROJECT_VISIBILITY` | visibility | `private` or `public` |
| `GITEA_OWNER` | Gitea user or organization | letters, digits, `.`, `_`, `-`; at most 39 |
| `USE_GITHUB` | also create a GitHub repository | `yes` or `no` |
| `GITHUB_OWNER` | GitHub user or organization | letters, digits, `-`; used only when GitHub is used |
| `PROJECT_DIRECTORY` | local directory | not empty, not starting with `-` |
| `ENABLE_PLAN_GATE` | enable the plan gate | `yes` or `no` |
- A key that is present counts as set, even when its value is empty. Only
`PROJECT_DESCRIPTION` may be empty (no description); an empty value for any
other key stops the run. Remove the line to be asked instead.
- An invalid value stops the run before any request to a host and names the
key. The script never falls back to asking for it.
- `USE_GITHUB=no` skips the GitHub owner and every GitHub step; a
`GITHUB_OWNER` set at the same time is ignored, with a warning.
- Only these eight details can be set. The confirmations stay questions that
default to no: create now, reusing an existing repository, an existing
directory, `core.hooksPath` and replacing a template file.
- These keys are accepted in `config.env` only, never in `.env`.
- A value is read as plain text: an unquoted ` #` starts a comment and cuts the
value there. Put a description that contains ` #` in double quotes, for
example `PROJECT_DESCRIPTION="Tool for #mirrors"`.
With all eight set, a run asks only the confirmations:
```bash
src/create-project.sh --apply # asks only "Create these now (y/n) [n]"
```
### `.env` (credentials) ### `.env` (credentials)
| Key | Meaning | | Key | Meaning |
@@ -109,7 +147,8 @@ src/create-project.sh --config /path/to/config.env --env /path/to/.env
The script asks for, in this order: repository name, description, visibility, The script asks for, in this order: repository name, description, visibility,
Gitea owner, whether to also create a GitHub repository (and its owner), the Gitea owner, whether to also create a GitHub repository (and its owner), the
local directory and whether to enable the plan gate. It then checks both hosts local directory and whether to enable the plan gate (a detail set in
[`config.env`](#configenv-project-details-optional) is not asked). It then checks both hosts
with read-only requests and prints a plan: with read-only requests and prints a plan:
```text ```text
+16
View File
@@ -30,6 +30,22 @@ GITEA_API_URL=https://<your gitea instance>/api/v1
# The server may enforce a minimum. Default: 10m0s. # The server may enforce a minimum. Default: 10m0s.
#MIRROR_INTERVAL=10m0s #MIRROR_INTERVAL=10m0s
# Optional. Project details that are set here are not asked. A key that is
# present counts as set, even when empty; only PROJECT_DESCRIPTION may be
# empty. An invalid value stops the run and names the key. Remove or comment
# out a line to be asked for it. The confirmations ("Create these now" and
# the questions about existing repositories, directories and files) are
# always asked. Put a value that contains " #" in double quotes: an unquoted
# " #" starts a comment.
#PROJECT_NAME=my-project
#PROJECT_DESCRIPTION=What the project is for
#PROJECT_VISIBILITY=private # private or public
#GITEA_OWNER=my-organization
#USE_GITHUB=yes # yes or no
#GITHUB_OWNER=my-organization # used only when USE_GITHUB is yes
#PROJECT_DIRECTORY=./my-project
#ENABLE_PLAN_GATE=no # yes or no
# Optional. OWNER/NAME of the SQA-QC-Framework repository on the Gitea server; # Optional. OWNER/NAME of the SQA-QC-Framework repository on the Gitea server;
# it is added to the new project as a submodule over SSH. # it is added to the new project as a submodule over SSH.
# Default: TirSystem/SQA-QC-Framework. # Default: TirSystem/SQA-QC-Framework.
+3 -2
View File
@@ -14,16 +14,17 @@ document of a type. `Primary File` may contain a glob (e.g.
| BC | Business Case | docs/business-case.md | 002 | | BC | Business Case | docs/business-case.md | 002 |
| SA | Stakeholder Analysis | docs/stakeholder-analysis.md | 002 | | SA | Stakeholder Analysis | docs/stakeholder-analysis.md | 002 |
| PP | Project Plan | docs/project-plan.md | 002 | | PP | Project Plan | docs/project-plan.md | 002 |
| MIL | Milestone / Gateway | docs/milestones/*.md | 005 | | MIL | Milestone / Gateway | docs/milestones/*.md | 006 |
| US | User Story | docs/user-stories.md | 002 | | US | User Story | docs/user-stories.md | 002 |
| UC | Use Case | docs/uc-*/uc.md | 002 | | UC | Use Case | docs/uc-*/uc.md | 002 |
| SSD | System Sequence Diagram | docs/uc-*/ssd.md | 002 | | SSD | System Sequence Diagram | docs/uc-*/ssd.md | 002 |
| OC | Operation Contract | docs/uc-*/oc.md | 002 | | OC | Operation Contract | docs/uc-*/oc.md | 002 |
| SD | Sequence Diagram | docs/uc-*/sd.md | 002 | | SD | Sequence Diagram | docs/uc-*/sd.md | 002 |
| DM | Domain Model | docs/domain-model.md | 003 | | 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 | | DICT | Domain Dictionary (PO and IT terms) | docs/dictionary.md | 002 |
| UCD | Use Case Diagram | docs/use-case-diagram.md | 002 | | UCD | Use Case Diagram | docs/use-case-diagram.md | 002 |
| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 019 | | RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 022 |
| TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 | | TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 |
## Languages ## Languages
+11 -5
View File
@@ -9,8 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-05 | Accepted | Jens Tirsvad Nielsen | S02 | Optional GitHub; choosing GitHub applies the AGPL license to the Gitea repository<br>Cited UCD-001<br>Justified the qualitative cost-benefit; stakeholder roles replaced by interests; success criteria 2 and 3 reworded for optional GitHub<br>Added objective 7 (documentation) and its success criterion | [02875ae] | | 2026-10-05 | Deprecated | Jens Tirsvad Nielsen | S02 | Added objective 8 (project details preset in config.env), the matching scope item and success criterion 8 | [2a6bb8e] |
| 2026-10-05 | Accepted | Jens Tirsvad Nielsen | S02 | Added objective 8 (project details preset in config.env), the matching scope item and success criterion 8 | [2a6bb8e] | | 2026-10-06 | Accepted | Jens Tirsvad Nielsen | S02 | Added objective 9 (credentials asked, project .env created), scope items, success criterion 9 and a risk<br>Objective 6 and success criterion 1 now allow a token only in the new project's .env | [ded26a6] |
--- ---
@@ -39,9 +39,10 @@ One repeatable, reviewed procedure gives every new project the same secure basel
3. When GitHub was chosen, configure the Gitea repository as a push mirror to GitHub (direction Gitea to GitHub). 3. When GitHub was chosen, configure the Gitea repository as a push mirror to GitHub (direction Gitea to GitHub).
4. Create the local project directory with an `origin` (Gitea) remote and, when GitHub was chosen, a `github` remote, neither containing credentials. 4. Create the local project directory with an `origin` (Gitea) remote and, when GitHub was chosen, a `github` remote, neither containing credentials.
5. Add the SQA-QC-Framework as the `framework` submodule, install its skills and git hooks, and copy its templates, optionally enabling the plan gate. 5. Add the SQA-QC-Framework as the `framework` submodule, install its skills and git hooks, and copy its templates, optionally enabling the plan gate.
6. Never print or persist a token, and never overwrite existing files or directories without consent. 6. Never print a token or put one in a URL, a remote or a log, write one to disk only in the new project's own `.env` and only after the Maintainer agrees, and never overwrite existing files or directories without consent.
7. Document installation, configuration, usage, security decisions and error handling in clear English for GitHub readers. 7. Document installation, configuration, usage, security decisions and error handling in clear English for GitHub readers.
8. Let the Maintainer preset the project details in `config.env`, so that a detail that is set there is not asked again. 8. Let the Maintainer preset the project details in `config.env`, so that a detail that is set there is not asked again.
9. Ask for a credential that is not provided in `.env` (`GITEA_TOKEN`, `GITHUB_PAT`, `GITHUB_USER`) and, when the Maintainer agrees, create a `.env` file with the credentials the new project needs.
## Scope ## Scope
@@ -52,6 +53,7 @@ One repeatable, reviewed procedure gives every new project the same secure basel
- Prompts for name, description, visibility and owner on each chosen host, and whether to use GitHub (which also applies the AGPL license). Each of these details may be set in `config.env` instead and is then not asked. - Prompts for name, description, visibility and owner on each chosen host, and whether to use GitHub (which also applies the AGPL license). Each of these details may be set in `config.env` instead and is then not asked.
- Checks for required tools (`git`, `curl`, optional `jq`) before any change. - Checks for required tools (`git`, `curl`, optional `jq`) before any change.
- A check that the project name is not already taken on GitHub. - A check that the project name is not already taken on GitHub.
- Asking for a credential that `.env` does not provide, and creating the new project's own `.env` (owner-only, ignored by git, never overwritten without a yes).
- Partial-failure reporting with a documented way to continue. - Partial-failure reporting with a documented way to continue.
- Documentation of the SSH prerequisite for the submodule (Gitea SSH on port `10022`). - Documentation of the SSH prerequisite for the submodule (Gitea SSH on port `10022`).
@@ -61,6 +63,7 @@ One repeatable, reviewed procedure gives every new project the same secure basel
- Managing repositories after creation (branch protection, webhooks, teams, CI). - Managing repositories after creation (branch protection, webhooks, teams, CI).
- Hosts other than GitHub and the configured Gitea instance. - Hosts other than GitHub and the configured Gitea instance.
- Creating or rotating tokens and SSH keys. - Creating or rotating tokens and SSH keys.
- Storing a credential anywhere but the new project's `.env` (no password manager, keychain or encryption).
- Making the first commit or opening a pull request. - Making the first commit or opening a pull request.
## Expected Benefits ## Expected Benefits
@@ -83,7 +86,7 @@ Supports developing on self-hosted Gitea while publishing to GitHub, and adoptin
| # | Criterion | Target | Measure | | # | Criterion | Target | Measure |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| 1 | Credential exposure | 0 occurrences of a token in output, saved remote URLs, config files or leftover temp files | Test run with log review; `git config --get-regexp remote` inspected | | 1 | Credential exposure | 0 occurrences of a token in output, saved remote URLs, tracked files, config files or leftover temp files; a token is written only to the new project's `.env` (owner-only, ignored by git) and only after a yes | Test run with log review; `git config --get-regexp remote` inspected; every file of the new project searched for the tokens |
| 2 | Repository ownership | Each repository created is under the owner chosen at the prompt for that host, never silently under `GITHUB_USER` | Test run with a user owner and with an organization owner | | 2 | Repository ownership | Each repository created is under the owner chosen at the prompt for that host, never silently under `GITHUB_USER` | Test run with a user owner and with an organization owner |
| 3 | Mirror direction | When GitHub is chosen, Gitea is the source and GitHub the target; a push to `origin` appears on GitHub | Push a test commit and compare | | 3 | Mirror direction | When GitHub is chosen, Gitea is the source and GitHub the target; a push to `origin` appears on GitHub | Push a test commit and compare |
| 4 | Partial failure | When one host fails, the output lists what was created and the command to continue | Forced failure test (invalid token for one host) | | 4 | Partial failure | When one host fails, the output lists what was created and the command to continue | Forced failure test (invalid token for one host) |
@@ -91,6 +94,7 @@ Supports developing on self-hosted Gitea while publishing to GitHub, and adoptin
| 6 | Lint | `shellcheck` reports no errors on `create-project.sh` | `shellcheck create-project.sh` | | 6 | Lint | `shellcheck` reports no errors on `create-project.sh` | `shellcheck create-project.sh` |
| 7 | Documentation | `README.md` covers installation, configuration, usage, security decisions, error handling and stakeholders | Review by S02 against MIL-003 Go/No-Go criterion 6 | | 7 | Documentation | `README.md` covers installation, configuration, usage, security decisions, error handling and stakeholders | Review by S02 against MIL-003 Go/No-Go criterion 6 |
| 8 | Preset details | A project detail set in `config.env` is never asked; an invalid one stops the run before any request and names the key | Tests with each key set, absent, empty and invalid | | 8 | Preset details | A project detail set in `config.env` is never asked; an invalid one stops the run before any request and names the key | Tests with each key set, absent, empty and invalid |
| 9 | Credentials asked and kept | A credential missing from `.env` is asked (not echoed) instead of stopping the run; the new project's `.env` is created only after a yes, owner-only, ignored by git, holding only the keys the project needs, and an existing `.env` is never replaced without a yes | Tests: each credential present and missing, `.env` written, declined, existing, file mode, git exclusion, no token in output |
## Risks ## Risks
@@ -100,6 +104,7 @@ Supports developing on self-hosted Gitea while publishing to GitHub, and adoptin
| GitHub PAT lacks permission to create repositories or to push | Creation or mirroring fails | Document the required scopes; check with a read-only API call first and stop with a clear message | | GitHub PAT lacks permission to create repositories or to push | Creation or mirroring fails | Document the required scopes; check with a read-only API call first and stop with a clear message |
| Gitea stores the mirror credentials server-side | A Gitea admin could access the GitHub token | Document it; recommend a fine-grained PAT limited to the one repository where possible | | Gitea stores the mirror credentials server-side | A Gitea admin could access the GitHub token | Document it; recommend a fine-grained PAT limited to the one repository where possible |
| SSH to Gitea port `10022` is not configured | Submodule add fails after repositories already exist | Check SSH reachability before creating anything; document the prerequisite | | SSH to Gitea port `10022` is not configured | Submodule add fails after repositories already exist | Check SSH reachability before creating anything; document the prerequisite |
| A token written to the new project's `.env` is plain text on disk and could be committed or copied by mistake | A leaked token gives access to the hosts | Ask first (default no), write only the keys the project needs, mode owner-only, exclude the file from git through `.git/info/exclude`, never overwrite an existing `.env` without a yes, never print the value, document the risk |
| Framework hook installer changes `core.hooksPath` | An existing hook setup is silently replaced | Inspect the current value first and ask for consent | | Framework hook installer changes `core.hooksPath` | An existing hook setup is silently replaced | Inspect the current value first and ask for consent |
| Repository name conflicts on a host | Creation fails midway | Check availability on both hosts before creating either | | Repository name conflicts on a host | Creation fails midway | Check availability on both hosts before creating either |
@@ -114,6 +119,7 @@ Supports developing on self-hosted Gitea while publishing to GitHub, and adoptin
- Bash only, with `git` and `curl` required and `jq` optional. - Bash only, with `git` and `curl` required and `jq` optional.
- `config.env` and `.env` are parsed, never `source`d. - `config.env` and `.env` are parsed, never `source`d.
- No `rm -rf`, and no token in any URL, log or remote. - No `rm -rf`, and no token in any URL, log or remote.
- A token on disk only in the new project's `.env`, created by the script with the Maintainer's consent.
- The framework under `framework/` is not edited from this project. - The framework under `framework/` is not edited from this project.
## Cost–Benefit Assessment ## Cost–Benefit Assessment
@@ -140,5 +146,5 @@ Proceed — the procedure is small, well bounded and removes a repeated, securit
[SA-001]: ./stakeholder-analysis.md [SA-001]: ./stakeholder-analysis.md
[UCD-001]: ./use-case-diagram.md [UCD-001]: ./use-case-diagram.md
[02875ae]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/02875aee5f2953473924074eea0056eb31af6b7a
[2a6bb8e]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/2a6bb8e8afadfe6ca4a621da30e44a372898ca62 [2a6bb8e]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/2a6bb8e8afadfe6ca4a621da30e44a372898ca62
[ded26a6]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/ded26a658c666bf29d84093cb352e3635e07719b
+337
View File
@@ -0,0 +1,337 @@
# 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 | Deprecated | Jens Tirsvad Nielsen | S02 | Initial version, from DCD-001 (UC-001) | [f4d611b] |
| 2026-10-06 | Accepted | Jens Tirsvad Nielsen | S02 | Added CredentialCollector, EnvFileWriter and EnvFile (from DCD-001) | [ded26a6] |
---
## 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 <<controller>> {
+startProjectCreation() : PromptSet
+provideProjectDetails(name : String, description : String, visibility : Visibility, giteaOwner : Owner, githubOwner : Owner [0..1], directory : Path, enablePlanGate : Boolean, writeEnvFile : Boolean) : Summary
}
class ConfigLoader {
+load(configFile : Path, envFile : Path) : Configuration
}
class CredentialCollector {
+collect(configuration : Configuration, kinds : String [1..3]) : Configuration
}
class EnvFileWriter {
+write(project : LocalProject, configuration : Configuration, hasGithub : Boolean) : EnvFile [0..1]
}
class ToolChecker {
+check(tools : String [1..*]) : ToolCheck
}
class Preflight {
+check(request : ProjectRequest) : PreflightResult
}
abstract class GitHost <<facade>> {
-name : String
-webAddress : String
-apiAddress : String
+verifyToken() : Boolean
+ownerAccepts(owner : Owner) : Boolean
+nameFree(name : String) : Boolean
}
class GiteaClient <<facade>> {
+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 <<facade>> {
+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 EnvFile {
-address : Path
-keys : String [1..3]
}
class Template {
-name : String
-isCopied : Boolean
}
class InstallResult <<dto>>
class Summary {
-createdItems : String [0..*]
-skippedItems : String [0..*]
-nextSteps : String [0..*]
}
ProjectCreator ..> ConfigLoader : creates
ProjectCreator ..> ToolChecker : creates
ProjectCreator ..> CredentialCollector : creates
ProjectCreator ..> EnvFileWriter : creates [0..1]
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 "0..*" --> "1" Run
Run "1" *-- "1" Configuration
Run "1" *-- "1" ToolCheck
Run "1" *-- "0..1" ProjectRequest
Run "1" --> "1" PromptSet : returns
Configuration "1" *-- "1..3" Credential
ProjectRequest "0..*" --> "1" Owner : giteaOwner
ProjectRequest "0..*" --> "0..1" Owner : githubOwner
ProjectRequest "1" *-- "0..1" PreflightResult
ProjectRequest "1" --> "0..1" GiteaRepository : stored in
ProjectRequest "1" --> "0..1" GitHubRepository : also stored in
ProjectRequest "1" --> "0..1" LocalProject : working copy
Summary "0..*" --> "1" ProjectRequest : reports on
Repository <|-- GiteaRepository
Repository <|-- GitHubRepository
Repository "0..*" --> "1" Owner : owned by
GiteaRepository "1" *-- "0..1" LicenseFile
PushMirror "0..*" --> "1" GiteaRepository : source
PushMirror "0..*" --> "1" GitHubRepository : target
PushMirror "0..*" --> "1" Credential : authorised by
LocalProject "1" *-- "1..2" Remote
Remote "0..*" --> "1" Repository : points to
LocalProject "1" *-- "1" Submodule
LocalProject "1" *-- "1" HookSetup
LocalProject "1" *-- "0..*" Template
LocalProject "1" *-- "0..1" EnvFile
EnvFile "0..*" --> "1..3" Credential : copy of
InstallResult "0..*" --> "1" Submodule
InstallResult "0..*" --> "1" HookSetup
InstallResult "0..*" --> "0..*" Template
ProjectRequest "0..*" --> "1" Visibility
Repository "0..*" --> "1" 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` |
| `CredentialCollector` | none (system concept) | Asks, without echo, for a credential that `.env` does not provide and validates it like one read from `.env`. | none | `collect` |
| `EnvFileWriter` | Credentials File | Creates the project's own `.env` with the credentials the project needs: owner-only, excluded from git, never replaced without a yes. | none | `write` |
| `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 |
| `EnvFile` | Credentials File | The `.env` of the project: a copy of the credentials it needs. | `address`, `keys` | 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, writeEnvFile) : Summary` | [OC-001] `provideProjectDetails`; [SD-001] `provideProjectDetails(...)` |
| `ConfigLoader.load(configFile, envFile) : Configuration` | [SD-001] `load(config.env, .env)`; [OC-001] `startProjectCreation` P2 |
| `CredentialCollector.collect(configuration, kinds) : Configuration` | [SD-001] `collect(configuration, GITEA_TOKEN)` and `collect(configuration, GITHUB_PAT, GITHUB_USER)`; [OC-001] `startProjectCreation` P2 and the precondition of `provideProjectDetails` |
| `EnvFileWriter.write(project, configuration, hasGithub) : EnvFile` | [SD-001] `write(localProject, configuration, githubOwner present)`; [OC-001] `provideProjectDetails` P14 |
| `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`, `CredentialCollector`, `EnvFileWriter`, `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. `CredentialCollector` and `EnvFileWriter` depend only on `Configuration`, `Credential` and `LocalProject`; the only class that holds a secret after the run is `EnvFile`, and only as a copy written to the Maintainer's own disk. `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` |
| `CredentialCollector` | planned for [MIL-005]: `lib/credentials.sh`, with `lib/prompts.sh` |
| `EnvFileWriter` | planned for [MIL-005]: `lib/envfile.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
[MIL-005]: ./milestones/mil-005-credentials.md
[DICT-001]: ./dictionary.md
[f4d611b]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/f4d611b77cc70b4686506d44bf8f439045d9e0d2
[ded26a6]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/ded26a658c666bf29d84093cb352e3635e07719b
+27 -19
View File
@@ -9,7 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-05 | Accepted | Jens Tirsvad Nielsen | S02 | Initial version, terms of UC-001 | [02875ae] | | 2026-10-06 | Deprecated | Jens Tirsvad Nielsen | S02 | The IT terms are also used in DCD-001 and DCD-002<br>InstallResult and Visibility named as design-only types | [f4d611b] |
| 2026-10-06 | Accepted | Jens Tirsvad Nielsen | S02 | Added Credentials File / EnvFile | [ded26a6] |
--- ---
@@ -21,23 +22,24 @@ 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | 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 | | Credentials File | en | EnvFile | The file in the local project that holds a copy of the credentials the project needs; owner-only and ignored by git. | 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 ## Rules
@@ -47,6 +49,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 - `Run`, `ToolCheck`, `PreflightResult` and `PromptSet` appear in [OC-001] but
have no PO term: they are system concepts, not domain concepts, and are not have no PO term: they are system concepts, not domain concepts, and are not
in the Domain Model. 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. - A new concept in a Domain Model gets a row here in the same change.
--- ---
@@ -56,4 +62,6 @@ Maps each Product Owner (PO) term to its professional IT term. PO language: Engl
[DM-001]: ./uc-001/dm.md [DM-001]: ./uc-001/dm.md
[DM-002]: ./domain-model.md [DM-002]: ./domain-model.md
[OC-001]: ./uc-001/oc.md [OC-001]: ./uc-001/oc.md
[02875ae]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/02875aee5f2953473924074eea0056eb31af6b7a [DCD-001]: ./uc-001/dcd.md
[f4d611b]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/f4d611b77cc70b4686506d44bf8f439045d9e0d2
[ded26a6]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/ded26a658c666bf29d84093cb352e3635e07719b
+11 -3
View File
@@ -9,8 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-05 | Accepted | Jens Tirsvad Nielsen | S02 | Initial version, created from [DM-001] (UC-001) | [02875ae] | | 2026-10-05 | Deprecated | Jens Tirsvad Nielsen | S02 | Configuration may hold preset project details (from DM-001, UC-001 step 3) | [2a6bb8e] |
| 2026-10-05 | Accepted | Jens Tirsvad Nielsen | S02 | Configuration may hold preset project details (from DM-001, UC-001 step 3) | [2a6bb8e] | | 2026-10-06 | Accepted | Jens Tirsvad Nielsen | S02 | Added Credentials File (from DM-001, UC-001 step 9) | [ded26a6] |
--- ---
@@ -79,6 +79,9 @@ class "Framework Setup" as FrameworkSetup {
class Template { class Template {
name name
} }
class "Credentials File" as CredentialsFile {
address
}
class Summary { class Summary {
created items created items
skipped items skipped items
@@ -108,6 +111,8 @@ LocalProject "1" --> "1" FrameworkSetup : has
FrameworkSetup "0..*" --> "1" Framework : is installed from FrameworkSetup "0..*" --> "1" Framework : is installed from
Framework "1" --> "1..*" Template : provides Framework "1" --> "1..*" Template : provides
LocalProject "1" --> "0..*" Template : contains a copy of LocalProject "1" --> "0..*" Template : contains a copy of
LocalProject "1" --> "0..1" CredentialsFile : has
CredentialsFile "1" --> "1..2" AccessToken : holds a copy of
Summary "1" --> "1" Project : reports on Summary "1" --> "1" Project : reports on
@enduml @enduml
``` ```
@@ -132,6 +137,7 @@ Summary "1" --> "1" Project : reports on
| Framework | The SQA-QC-Framework added to a Local Project | name, address | [UC-001] step 9 "framework submodule" | | Framework | The SQA-QC-Framework added to a Local Project | name, address | [UC-001] step 9 "framework submodule" |
| Framework Setup | The skills and git hooks installed from the Framework, with the plan gate on or off | plan gate enabled | [UC-001] step 9 "skills and hooks", "plan gate" | | Framework Setup | The skills and git hooks installed from the Framework, with the plan gate on or off | plan gate enabled | [UC-001] step 9 "skills and hooks", "plan gate" |
| Template | A file the Framework provides to copy into a project (`AGENTS.md`, artifact registry) | name | [UC-001] step 9 "templates" | | Template | A file the Framework provides to copy into a project (`AGENTS.md`, artifact registry) | name | [UC-001] step 9 "templates" |
| Credentials File | The file in a Local Project that holds a copy of the Access Tokens (and the GitHub account name) the project needs; readable by its owner only and ignored by git | address | [UC-001] step 9 "credentials file" |
| Summary | The report of what was created, skipped or failed and how to continue | created items, skipped items, next steps | [UC-001] step 10 "summary" | | Summary | The report of what was created, skipped or failed and how to continue | created items, skipped items, next steps | [UC-001] step 10 "summary" |
## Association Table ## Association Table
@@ -158,6 +164,8 @@ Summary "1" --> "1" Project : reports on
| Framework Setup | is installed from | Framework | 0..* to 1 | | Framework Setup | is installed from | Framework | 0..* to 1 |
| Framework | provides | Template | 1 to 1..* | | Framework | provides | Template | 1 to 1..* |
| Local Project | contains a copy of | Template | 1 to 0..* | | Local Project | contains a copy of | Template | 1 to 0..* |
| Local Project | has | Credentials File | 1 to 0..1 |
| Credentials File | holds a copy of | Access Token | 1 to 1..2 |
| Summary | reports on | Project | 1 to 1 | | Summary | reports on | Project | 1 to 1 |
## Generalizations ## Generalizations
@@ -172,5 +180,5 @@ Summary "1" --> "1" Project : reports on
[SSD-001]: ./uc-001/ssd.md [SSD-001]: ./uc-001/ssd.md
[DICT-001]: ./dictionary.md [DICT-001]: ./dictionary.md
[DM-001]: ./uc-001/dm.md [DM-001]: ./uc-001/dm.md
[02875ae]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/02875aee5f2953473924074eea0056eb31af6b7a
[2a6bb8e]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/2a6bb8e8afadfe6ca4a621da30e44a372898ca62 [2a6bb8e]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/2a6bb8e8afadfe6ca4a621da30e44a372898ca62
[ded26a6]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/ded26a658c666bf29d84093cb352e3635e07719b
+82
View File
@@ -0,0 +1,82 @@
# MIL-005 Credentials
## Metadata
| Key | Value |
| --- | --- |
| ID | MIL-005 |
| CrossReference | [BC-001], [US-001], [UC-001], [DCD-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-06 | Accepted | Jens Tirsvad Nielsen | S02 | Initial version | [ded26a6] |
---
## Purpose
Decide whether the script can ask for a credential that `.env` does not provide and create the new project's own `.env`, without exposing a token: asked without echo, written only after a yes, owner-only, ignored by git, never replacing an existing file, and never shown in any output.
## Deliverable
`create-project.sh` that (1) no longer stops when `.env` is missing or lacks a credential but asks for it without echo (`GITEA_TOKEN` always; `GITHUB_PAT` and `GITHUB_USER` once GitHub is chosen), validating each value like one read from `.env`; and (2) after the local project exists, asks whether to create a `.env` in it and, on a yes, writes only the keys the project needs: `GITEA_TOKEN`, and `GITHUB_PAT` and `GITHUB_USER` when GitHub was chosen. The file is readable by its owner only, excluded from git through `.git/info/exclude` (no tracked file changes, nothing is committed) and never replaced without a yes. The README and `.env.example` document it. The tests cover every case.
This changes a security guarantee of the earlier milestones ("a token is never persisted"): a token may now be written to one file, and only after a yes. [BC-001] objective 6, success criterion 1 and the risk table are amended in the same change.
## Go / No-Go Criteria
| # | Criterion (objectively checkable) | Go | No-Go |
| --- | --- | --- | --- |
| 1 | `GITEA_TOKEN` missing from `.env`, or `.env` absent: it is asked, not echoed, validated like a token read from `.env`, and the run continues; the same for `GITHUB_PAT` and `GITHUB_USER` once GitHub is chosen, and never for them when GitHub is not chosen | Tests pass | The run stops with an error, a value is echoed or GitHub credentials are asked without GitHub |
| 2 | An invalid asked value is refused and asked again without showing it; when input ends the run stops before any request to a host and names the key | Tests pass | A request made or a value shown |
| 3 | A credential provided in `.env` is not asked | Tests pass | Any prompt shown |
| 4 | The "create `.env`" question is asked after the local project exists and defaults to no; on no, no file is created and the summary says so | Tests pass | A file written without a yes |
| 5 | On yes the new project's `.env` exists, holds exactly the needed keys (`GITEA_TOKEN`; plus `GITHUB_PAT` and `GITHUB_USER` when GitHub was chosen), has an owner-only mode (600) from the moment it is created, and `git status` in the project does not list it | Tests pass | Another key, another mode or the file listed |
| 6 | An existing `.env` in the project is never replaced without a yes; on no it is kept unchanged and reported | Tests pass | Any replaced without a yes |
| 7 | No token appears in any output, summary, log, remote URL, tracked file or file other than the project's `.env`; searched in every file of the new project and in all output of the tests, including under `bash -x` | Tests pass | Any hit |
| 8 | Only the project's `.env` changed on disk by this feature: no tracked file, no `.gitignore`, no global git configuration is written | Tests pass | Any other change |
| 9 | All acceptance criteria of US-001.05 in [US-001] are met | Verified | Any unmet |
## Dependencies
| Depends on | Reason |
| --- | --- |
| [MIL-004] | Needs the prompt flow and the configuration presets it changed |
## Traceability
| Business Case objective / KPI / user story | Reference |
| --- | --- |
| User story US-001.05 | [US-001] |
| Objective 9 (credentials asked, project `.env`) and the amended objective 6 | [BC-001] |
| Success criteria 1 and 9 | [BC-001] |
## Ownership
| Role | Stakeholder ID (SA) |
| --- | --- |
| Owner | S01 |
| Approving reviewer | S02 |
## Target Date
2026-11-27 — proposed; the Business Case sets no deadline.
## Tasks
| # | Task | Summary | Needs its own Use Case/User Story? | Reference |
| --- | --- | --- | --- | --- |
| 1 | Ask for a credential that `.env` does not provide | `.env` becomes optional. `GITEA_TOKEN` is asked after the configuration is read; `GITHUB_PAT` and `GITHUB_USER` once GitHub is chosen. Read without echo (`read -s`), validated by the existing token and account validators, registered for redaction before any later message, asked again when invalid, a stop naming the key when input ends. A new `lib/credentials.sh` (class `CredentialCollector` of [DCD-001]). Extension 2b of [UC-001]. | Yes | [UC-001] |
| 2 | Create the new project's `.env` | After the local project exists and only after a yes (default no): write the needed keys to `.env` created with `umask 077` and mode 600, never replacing an existing file without a yes, and add `.env` to `.git/info/exclude`. Report it in the summary without showing a value. A new `lib/envfile.sh` (class `EnvFileWriter`). Step 9 and extensions 9c and 9d of [UC-001]. | Yes | [UC-001] |
| 3 | Keep every token out of everything else | The `bash -x` guard, the redaction and the temporary files keep working with credentials that are asked; the asked value never reaches a command line, output, summary or a file other than the project's `.env`. Add the new function groups to the library layout. | No | |
| 4 | Document the feature and its risk | README: credentials may be asked, the project `.env`, what it holds, why it is owner-only and ignored, the risk of a plain-text token on disk, how to say no. `.env.example` and the security decisions section updated. | No | |
| 5 | Test every case | Each credential present, missing and invalid; `.env` absent; no GitHub credentials without GitHub; end of input; the question defaults to no; file contents, mode and git exclusion; an existing `.env`; every token searched for in the output and the project; the existing tests unchanged. | No | |
---
[BC-001]: ../business-case.md
[US-001]: ../user-stories.md
[UC-001]: ../uc-001/uc.md
[DCD-001]: ../uc-001/dcd.md
[MIL-004]: ./mil-004-configurable-details.md
[ded26a6]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/ded26a658c666bf29d84093cb352e3635e07719b
+12 -7
View File
@@ -4,23 +4,23 @@
| Key | Value | | Key | Value |
| --- | --- | | --- | --- |
| ID | PP-001 | | ID | PP-001 |
| CrossReference | [BC-001], [SA-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [US-001] | | CrossReference | [BC-001], [SA-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [US-001] |
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-05 | Accepted | Jens Tirsvad Nielsen | S02 | Stories per phase: US-001.01 to US-001.03<br>Dates accepted | [02875ae] | | 2026-10-05 | Deprecated | Jens Tirsvad Nielsen | S02 | Added phase MIL-004 (proposed dates 2026-11-16 to 2026-11-20) | [2a6bb8e] |
| 2026-10-05 | Accepted | Jens Tirsvad Nielsen | S02 | Added phase MIL-004 (proposed dates 2026-11-16 to 2026-11-20) | [2a6bb8e] | | 2026-10-06 | Accepted | Jens Tirsvad Nielsen | S02 | Added phase MIL-005 (proposed dates 2026-11-23 to 2026-11-27) | [ded26a6] |
--- ---
## Purpose ## Purpose
Schedule the four phases that deliver RepoFoundry (`create-project.sh` and its documentation) in dependency order. The Business Case sets no deadline, so the dates below are proposals for S01 to confirm. Schedule the five phases that deliver RepoFoundry (`create-project.sh` and its documentation) in dependency order. The Business Case sets no deadline, so the dates below are proposals for S01 to confirm.
## Planning Assumptions ## Planning Assumptions
- Week 1 starts 2026-10-05; the plan ends by 2026-11-20 (the last phase is proposed). - Week 1 starts 2026-10-05; the plan ends by 2026-11-27 (the last phase is proposed).
- Phase length: two weeks. - Phase length: two weeks.
- S01 and S02 review each phase through a pull request, as described in [SA-001]. For now one person holds both roles. - S01 and S02 review each phase through a pull request, as described in [SA-001]. For now one person holds both roles.
- The PO language is English, so no translated copies are kept. - The PO language is English, so no translated copies are kept.
@@ -33,6 +33,7 @@ Schedule the four phases that deliver RepoFoundry (`create-project.sh` and its d
| Repositories and Mirror | [MIL-002] | 2026-10-19 to 2026-10-30 | 2026-10-30 | S02 | US-001.02 | GitHub and Gitea repositories and the push mirror | [Milestone 44] | | Repositories and Mirror | [MIL-002] | 2026-10-19 to 2026-10-30 | 2026-10-30 | S02 | US-001.02 | GitHub and Gitea repositories and the push mirror | [Milestone 44] |
| Scaffold and Release | [MIL-003] | 2026-11-02 to 2026-11-13 | 2026-11-13 | S01 | US-001.03 | Local project, framework, README, final review | [Milestone 45] | | Scaffold and Release | [MIL-003] | 2026-11-02 to 2026-11-13 | 2026-11-13 | S01 | US-001.03 | Local project, framework, README, final review | [Milestone 45] |
| Configurable Details | [MIL-004] | 2026-11-16 to 2026-11-20 | 2026-11-20 | S01 | US-001.04 | Project details preset in config.env | | | Configurable Details | [MIL-004] | 2026-11-16 to 2026-11-20 | 2026-11-20 | S01 | US-001.04 | Project details preset in config.env | |
| Credentials | [MIL-005] | 2026-11-23 to 2026-11-27 | 2026-11-27 | S01 | US-001.05 | Missing credentials asked; project .env | |
```plantuml ```plantuml
@startgantt @startgantt
@@ -45,6 +46,8 @@ Project starts 2026-10-05
[Scaffold and Release Go/No-Go] happens 2026-11-13 [Scaffold and Release Go/No-Go] happens 2026-11-13
[Configurable Details] starts 2026-11-16 and ends 2026-11-20 [Configurable Details] starts 2026-11-16 and ends 2026-11-20
[Configurable Details Go/No-Go] happens 2026-11-20 [Configurable Details Go/No-Go] happens 2026-11-20
[Credentials] starts 2026-11-23 and ends 2026-11-27
[Credentials Go/No-Go] happens 2026-11-27
@endgantt @endgantt
``` ```
@@ -59,11 +62,12 @@ Project starts 2026-10-05
| Local directory, remotes, framework submodule, skills, hooks, templates, plan gate | [MIL-003] | | Local directory, remotes, framework submodule, skills, hooks, templates, plan gate | [MIL-003] |
| README and SSH prerequisite documentation | [MIL-003] | | README and SSH prerequisite documentation | [MIL-003] |
| Project details set in `config.env` instead of asked | [MIL-004] | | Project details set in `config.env` instead of asked | [MIL-004] |
| Missing credentials asked; the new project's `.env` | [MIL-005] |
## Dependencies ## Dependencies
``` ```
MIL-001 → MIL-002 → MIL-003 → MIL-004 MIL-001 → MIL-002 → MIL-003 → MIL-004 → MIL-005
``` ```
A No-Go moves every later date by the time needed to rework the failed criteria. A No-Go moves every later date by the time needed to rework the failed criteria.
@@ -91,11 +95,12 @@ A No-Go moves every later date by the time needed to rework the failed criteria.
[MIL-002]: ./milestones/mil-002-repositories-and-mirror.md [MIL-002]: ./milestones/mil-002-repositories-and-mirror.md
[MIL-003]: ./milestones/mil-003-scaffold-and-release.md [MIL-003]: ./milestones/mil-003-scaffold-and-release.md
[MIL-004]: ./milestones/mil-004-configurable-details.md [MIL-004]: ./milestones/mil-004-configurable-details.md
[MIL-005]: ./milestones/mil-005-credentials.md
[US-001]: ./user-stories.md [US-001]: ./user-stories.md
[UC-001]: ./uc-001/uc.md [UC-001]: ./uc-001/uc.md
[SSD-001]: ./uc-001/ssd.md [SSD-001]: ./uc-001/ssd.md
[02875ae]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/02875aee5f2953473924074eea0056eb31af6b7a
[Milestone 43]: https://git.tirsystem.com/TirSystem-BashScript/RepoFoundry/milestone/43 [Milestone 43]: https://git.tirsystem.com/TirSystem-BashScript/RepoFoundry/milestone/43
[Milestone 44]: https://git.tirsystem.com/TirSystem-BashScript/RepoFoundry/milestone/44 [Milestone 44]: https://git.tirsystem.com/TirSystem-BashScript/RepoFoundry/milestone/44
[Milestone 45]: https://git.tirsystem.com/TirSystem-BashScript/RepoFoundry/milestone/45 [Milestone 45]: https://git.tirsystem.com/TirSystem-BashScript/RepoFoundry/milestone/45
[2a6bb8e]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/2a6bb8e8afadfe6ca4a621da30e44a372898ca62 [2a6bb8e]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/2a6bb8e8afadfe6ca4a621da30e44a372898ca62
[ded26a6]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/ded26a658c666bf29d84093cb352e3635e07719b
+70
View File
@@ -0,0 +1,70 @@
# SQA Review Record: Shell code review of the MIL-004 change
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-019 |
| CrossReference | [MIL-004], [QC-SH-001], [RC-016] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-05 | Proposed | Jens Tirsvad Nielsen | S02 | Initial version | [ceaa7d1] |
---
## Artifact Under Review
- Instance reviewed: the MIL-004 change to `src/create-project.sh` and `src/lib/` (`config.sh`, `constants.sh`, `project.sh`), `tests/test-presets.sh`, the README and `config.env.example`, on branch `mil-004-configurable-details` (commit `75e8e91` plus the fixes listed below), tasks 1 to 5 (issues #27 to #31) of [MIL-004].
- Checklist used: [QC-SH-001]. The rest of the code was reviewed in [RC-016].
- Review date: 2026-10-05
- Tool versions: bash 5.2.37, shellcheck 0.11.0, shfmt 3.14.1 (Windows, Git Bash)
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | Starts with `#!/usr/bin/env bash` and `set -euo pipefail` | Pass | Unchanged. |
| 2 | Every expansion is quoted; lists are arrays; tests use `[[ ]]` and `$(...)` | Pass | `shellcheck` is clean. Values from `config.env` are only ever compared, matched against validators or printed; none reaches `eval`, a command line or a file name before it passed a validator. |
| 3 | Names follow the conventions | Pass | `check_preset`, `preset_detail`, `source_note` and the `HINT_*` constants follow the rules. See finding F4 on the name `PROJECT_NAME`. |
| 4 | Passes `shellcheck` and `bash -n` with no unexplained `disable` comments | Pass | No new `disable`. |
| 5 | Errors go to standard error with an `error:` message and a non-zero exit code | Pass | Every refusal goes through `die`, names the key and the file, never the value. |
| 6 | Temporary files use `mktemp` with a `trap` cleanup | Pass | Not touched. |
| 7 | No secret is written in the script, echoed, or put on a command line | Pass | The new keys carry no credential; they are rejected in `.env`, and credential keys stay rejected in `config.env` (tests). The description is printed in the summary, as it was when asked. |
| 8 | A script that changes state defaults to a dry run | Pass | Unchanged: a preset never skips the dry run or "Create these now". Tests prove that with all eight keys set, an empty or missing answer creates nothing. |
| 9 | A header comment states purpose, usage, options, environment variables and exit codes | Pass | Fixed during this review: the header said only "asks for the project details"; it now says that details set in `config.env` are not asked. |
| 10 | The script implements a task or design it cites; deviations are recorded | Pass | Tasks 1 to 5 of [MIL-004] and extensions 3a and 3b of [UC-001]. Deviations: values are accepted in any case, and `USE_GITHUB`/`ENABLE_PLAN_GATE` take `yes` or `no` only; both are in the README. |
| 11 | Behaviour is tested for success, failure and any disabled or bypass path | Pass | 919 checks in the full suite before the review, 0 failed. New: each key set, absent, empty and invalid; mixed asked and preset; `USE_GITHUB` interplay; the summary marker; no prompt text for a preset; the confirmations. Mutation check: making the preset lookup always fail made the tests fail. |
| 12 | Formatted with `shfmt` | Pass | No difference. |
| 13 | Safe to re-run | Pass | No state is kept. |
| 14 | Bash version and external tools stated | Pass | Unchanged. |
## Findings
| # | Finding | Severity | Status |
| --- | --- | --- | --- |
| F1 | An unquoted `PROJECT_DESCRIPTION` containing ` #` is silently cut at the comment mark (`Tool # for mirrors` becomes `Tool`), with no message. This is how the parser reads every value, but a description is the one free-text key, so it is where it bites. A value with both kinds of quote cannot be set at all (it can still be typed at the prompt). | Low (surprise, no data loss or security effect) | Fixed: the README and `config.env.example` say to put such a value in double quotes; a test pins both behaviours. The both-quotes case is documented as a limit of the parser. |
| F2 | The header of `create-project.sh` and the README sentence "The script asks for, in this order" did not mention that preset details are not asked. | Low (documentation) | Fixed. |
| F3 | The summary marks the source after the value, so a fully preset run reads `my-app (from config.env) (private (from config.env))`. Correct but noisy, and `USE_GITHUB=yes` is not marked on the GitHub line (only the owner is). | Low (readability) | Accepted: the marker is asserted by the tests and the wording is not a requirement; revisit if the Maintainer wants a table layout. |
| F4 | The constant `PROJECT_NAME` (the name of this tool, `RepoFoundry`) and the config key `PROJECT_NAME` (the name of the new project) share a spelling. They never meet in code (the key lives in `CONFIG`), but a reader can confuse them. | Low (maintainability) | Open: renaming the constant is out of scope for this change; a candidate for a later clean-up. |
| F5 | A `config.env` that sets `PROJECT_NAME` and `GITEA_OWNER` makes every run use them. Existing repositories are still detected and need the reuse confirmation, so nothing is overwritten. | Info | Documented in the README (per-project configuration). |
No finding affects credentials, ownership, the mirror direction or the confirmations.
## Overall Verdict
Go — all mandatory criteria pass after the fixes for F1 and F2 (found and fixed during this review; the test for F1 was added; the full suite was rerun afterwards: 923 checks, 0 failed). F3 and F4 are recorded and not blocking. Author and reviewer are the same person for now (S01 and S02 are both held by the Maintainer), so the framework independence rule is not met; re-review when a second person takes S02.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| Decide whether to rename the constant `PROJECT_NAME` (F4) and whether to restyle the summary markers (F3) | S02 | 2026-10-30 |
---
[MIL-004]: ../../milestones/mil-004-configurable-details.md
[UC-001]: ../../uc-001/uc.md
[RC-016]: ./rc-016-create-project-sh.md
[QC-SH-001]: ../../../framework/qc/qc-programming-shell.md
[ceaa7d1]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/ceaa7d18d8908b4d1e3fb089f238c99b883d71e0
+74
View File
@@ -0,0 +1,74 @@
# SQA Review Record: MIL-005 and the planning change it causes
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-020 |
| CrossReference | [MIL-005], [QC-MIL-001], [US-001], [UC-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-06 | Proposed | Jens Tirsvad Nielsen | S02 | Initial version | [ded26a6] |
---
## Artifact Under Review
- Instance reviewed: [MIL-005], and the changes it causes in [BC-001], [US-001], [UC-001], [SSD-001], [OC-001], [SD-001], [DM-001], [DM-002], [DICT-001] and [PP-001]. The two Design Class Diagrams that change with it are reviewed in [RC-021].
- Checklist used: [QC-MIL-001] for [MIL-005]. The other artifacts were changed, not created; their change is checked below for consistency with the checklists of their types and with the PP reference.
- Review date: 2026-10-06
## Checklist Results ([MIL-005], QC-MIL-001)
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | A concrete deliverable is defined for every gate | Pass | A script that asks for a missing credential and creates the new project's `.env`, the documentation of the feature and its risk, and tests for every case. |
| 2 | Explicit Go/No-Go criteria are stated for each gate | Pass | Nine criteria, each with an objective Go and No-Go, including all acceptance criteria of US-001.05. |
| 3 | Dependencies on other milestones are explicitly mapped | Pass | Depends on [MIL-004], with the reason. |
| 4 | Each milestone is traceable to a Business Case objective or KPI | Pass | Maps to objective 9, the amended objective 6 and success criteria 1 and 9 of [BC-001], and to US-001.05. |
| 5 | Milestone owner and approving reviewer are identified | Pass | Owner S01, approving reviewer S02. |
| 6 | Milestone has a defined target date consistent with project constraints | Pass | 2026-11-27 matches [PP-001]; the Business Case sets no duration, so nothing conflicts. The date is a proposal and is accepted here. |
## Change checks on the other artifacts
| Artifact | Change | Status | Evidence/Notes |
| --- | --- | --- | --- |
| [BC-001] | Objective 9, scope items, success criterion 9, a risk and a constraint; objective 6 and success criterion 1 amended | Pass | The security guarantee is weakened on purpose and said so in the same place: a token may be written only to the new project's `.env`, after a yes. The risk row lists the mitigations. |
| [US-001] | US-001.05 with five acceptance criteria | Pass | Given/when/then; traces to [UC-001] and [MIL-005]; the last criterion keeps the "no credential in output" rule. |
| [UC-001] | Precondition, step 2 and 9 notes, extensions 2b, 9c, 9d, a postcondition and two business rules | Pass | Extension 2b ends before any change when input ends; 9c and 9d keep "never replaced without a yes". |
| [SSD-001] | `writeEnvFile` and the credentials not provided in `.env` become parameters; the lifecycle note names the one thing that persists | Pass | One new parameter and a note; the operations are unchanged. |
| [OC-001] | Postcondition P14, a precondition, two exceptions, and P2 reworded | Pass | P14 states the contents, the mode, the git exclusion and "no credential shown". |
| [SD-001] | `CredentialCollector` and `EnvFileWriter` and their messages | Pass | Every new postcondition has a message; the coverage table is updated. Three lines damaged by an earlier edit (`actor Maintainer`, the first `provideProjectDetails` message, the final `summary` return) are restored in this change. |
| [DM-001], [DM-002] | Concept `Credentials File` and two associations | Pass | Both models changed in the same way. |
| [DICT-001] | `Credentials File` ↔ `EnvFile` | Pass | One PO term and one IT term, as the dictionary rules require. |
| [PP-001] | Phase [MIL-005], dependency chain, timeline | Pass | Dates agree with [MIL-005]. |
One point for the implementation: the Go/No-Go criterion 5 says the file has mode 600 "from the moment it is created". That means the script must create it under `umask 077` (or with `install -m 600`) and not write it first and restrict it afterwards. Task 2 already says so.
## Overall Verdict
Go — [MIL-005] passes every mandatory criterion and the changes to the other artifacts are consistent with each other. The weakening of the credential guarantee is deliberate, bounded and recorded in the Business Case. Author and reviewer are the same person for now (S01 and S02 are both held by the Maintainer), so the framework independence rule is not met; re-review when a second person takes S02.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| None | - | - |
---
[MIL-005]: ../../milestones/mil-005-credentials.md
[BC-001]: ../../business-case.md
[US-001]: ../../user-stories.md
[UC-001]: ../../uc-001/uc.md
[SSD-001]: ../../uc-001/ssd.md
[OC-001]: ../../uc-001/oc.md
[SD-001]: ../../uc-001/sd.md
[DM-001]: ../../uc-001/dm.md
[DM-002]: ../../domain-model.md
[DICT-001]: ../../dictionary.md
[PP-001]: ../../project-plan.md
[RC-021]: ./rc-021-dcd.md
[QC-MIL-001]: ../../../framework/qc/qc-milestones-gateways.md
[ded26a6]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/ded26a658c666bf29d84093cb352e3635e07719b
+64
View File
@@ -0,0 +1,64 @@
# SQA Review Record: Design Class Diagrams DCD-001 and DCD-002
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-021 |
| CrossReference | [DCD-001], [DCD-002], [QC-DCD-001], [SD-001], [OC-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-06 | Proposed | Jens Tirsvad Nielsen | S02 | Initial version | [ded26a6] |
---
## Artifact Under Review
- Instances reviewed: [DCD-001] (use case UC-001) and [DCD-002] (the consolidated project model). [DCD-002] was created from [DCD-001] and the two have the same classes, relationships and tables.
- Checklist used: [QC-DCD-001]
- Review date: 2026-10-06
- PlantUML: the diagrams were not rendered. `render-diagrams.sh` needs a PlantUML server and sends the diagram text to it; none is configured. The syntax was read by hand (see finding F3).
## Checklist Results
| # | Criterion | Level | Status | Evidence/Notes |
| --- | --- | --- | --- | --- |
| 1 | SOLID principles applied; no god classes | Mandatory | Pass | Each class has one reason to change: one host API each (`GiteaClient`, `GitHubClient`), local work (`LocalProjectBuilder`), the framework (`FrameworkInstaller`), prompts for credentials (`CredentialCollector`), the project `.env` (`EnvFileWriter`), the report (`SummaryReport`). `ProjectCreator` has two operations and no data. The clients share `GitHost` instead of repeating its three operations. |
| 2 | Visibility markers correct and consistent | Mandatory | Pass | Every attribute and operation has `+` or `-`; the only private operation is `GiteaClient.requestSync`, which no other class calls. Enumeration literals carry no marker, as is usual. |
| 3 | Association, aggregation, composition and dependency correctly distinguished | Mandatory | Pass | Composition where the part cannot outlive the whole (`Run`, `Configuration`, `LocalProject` and their parts); plain association for the links between independent objects; dependency for "creates" and "asks"; generalization for `Repository` and `GitHost`. No aggregation is used. |
| 4 | Multiplicities and navigability specified on all associations | Mandatory | Pass after fix | Found during this review: most associations gave only the target multiplicity. Fixed: both ends now carry a multiplicity and every association has one arrow. Dependencies carry none, as UML does not give them one. |
| 5 | Applied design patterns annotated | Optional | Pass | The Pattern Annotations table names Controller, Facade, Pure Fabrication, Creator, Protection from variations and a data transfer object. |
| 6 | Method signatures traceable to Operation Contracts and Sequence Diagrams | Mandatory | Pass | The Method Traceability table has a row for each of the 20 operations, each naming the [SD-001] message and the contract postcondition. [SD-001] was aligned in the same change (`createRepository(request, license)`, `compose(request)` and others). |
| 7 | Class names consistent with the Domain Model concepts they refine | Mandatory | Pass | The IT terms of [DICT-001] are used (`ProjectRequest` for Project, `Credential` for Access Token, `EnvFile` for Credentials File, and so on). `GitHost` is a class of its own, as the dictionary has one IT term for the PO term. The system concepts without a PO term (`Run`, `ToolCheck`, `PreflightResult`, `PromptSet`, `InstallResult`) are marked as such in the class table. |
| 8 | No circular dependencies | Optional | Pass | Stated and argued in the Dependency Check; `Summary` points at `ProjectRequest` and nothing points back; the helper classes depend on the data classes and the controller, never the other way round. |
## Findings
| # | Finding | Severity | Status |
| --- | --- | --- | --- |
| F1 | Associations gave only the target multiplicity (criterion 4). | Defect | Fixed in both files. |
| F2 | The planned classes `CredentialCollector`, `EnvFileWriter` and `EnvFile` (milestone [MIL-005]) are already in the diagram while the code does not exist yet. | Info | Accepted: the Implementation Mapping marks them "planned for MIL-005", so the diagram is a design for code still to come. |
| F3 | The PlantUML text was not rendered by a tool. | Low | Open: render with `render-diagrams.sh` once a server is chosen. Constructs used are standard (`abstract class`, `enum`, stereotypes, multiplicities, `skinparam`, `hide empty members`). |
| F4 | `Credential` is also the kind of `GITHUB_USER`, which is an account name, not a secret. The dictionary maps Access Token to `Credential`. | Info | Accepted: `kind` tells them apart; `Configuration` holds one to three of them. |
## Overall Verdict
Go — all mandatory criteria pass after the fix for criterion 4, and the optional ones pass. F3 is open and not blocking. Author and reviewer are the same person for now (S01 and S02 are both held by the Maintainer), so the framework independence rule is not met; re-review when a second person takes S02.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| Render the diagrams of [DCD-001], [DCD-002], [SD-001] and the other PlantUML blocks with `render-diagrams.sh` once the Maintainer chooses a server | S01 | 2026-10-16 |
---
[DCD-001]: ../../uc-001/dcd.md
[DCD-002]: ../../dcd.md
[SD-001]: ../../uc-001/sd.md
[OC-001]: ../../uc-001/oc.md
[DICT-001]: ../../dictionary.md
[MIL-005]: ../../milestones/mil-005-credentials.md
[QC-DCD-001]: ../../../framework/qc/qc-dcd.md
[ded26a6]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/ded26a658c666bf29d84093cb352e3635e07719b
+25 -16
View File
@@ -9,8 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-05 | Proposed | Jens Tirsvad Nielsen | S02 | Initial version, UC-001 artifacts and baseline | [02875ae] | | 2026-10-06 | Proposed | Jens Tirsvad Nielsen | S02 | Added DCD-001 and DCD-002 | [f4d611b] |
| 2026-10-05 | Proposed | Jens Tirsvad Nielsen | S02 | Added MIL-004 and RC-018 | [2a6bb8e] | | 2026-10-06 | Proposed | Jens Tirsvad Nielsen | S02 | Added MIL-005, RC-020 and RC-021 | [ded26a6] |
--- ---
@@ -24,27 +24,30 @@ updated whenever an artifact instance is created or reviewed.
| Artifact Instance | Type | Upstream (Backward Link) | Downstream (Forward Link) | Last Reviewed (RC-ID) | | Artifact Instance | Type | Upstream (Backward Link) | Downstream (Forward Link) | Last Reviewed (RC-ID) |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| [BC-001] | BC | - | [SA-001], [PP-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [US-001], [UCD-001] | [RC-010], [RC-018] | | [BC-001] | BC | - | [SA-001], [PP-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [US-001], [UCD-001] | [RC-010], [RC-018], [RC-020] |
| [SA-001] | SA | [BC-001] | [UCD-001], [UC-001], [DICT-001] | [RC-013] | | [SA-001] | SA | [BC-001] | [UCD-001], [UC-001], [DICT-001] | [RC-013] |
| [PP-001] | PP | [BC-001], [SA-001] | [MIL-001], [MIL-002], [MIL-003], [MIL-004] | [RC-012], [RC-018] | | [PP-001] | PP | [BC-001], [SA-001] | [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005] | [RC-012], [RC-018], [RC-020] |
| [MIL-001] | MIL | [BC-001], [PP-001] | [US-001] | [RC-011], [RC-016] | | [MIL-001] | MIL | [BC-001], [PP-001] | [US-001] | [RC-011], [RC-016] |
| [MIL-002] | MIL | [BC-001], [PP-001] | [US-001] | [RC-014], [RC-017] | | [MIL-002] | MIL | [BC-001], [PP-001] | [US-001] | [RC-014], [RC-017] |
| [MIL-003] | MIL | [BC-001], [PP-001] | [US-001] | [RC-015], [RC-017] | | [MIL-003] | MIL | [BC-001], [PP-001] | [US-001] | [RC-015], [RC-017] |
| [MIL-004] | MIL | [BC-001], [PP-001] | [US-001] | [RC-018] | | [MIL-004] | MIL | [BC-001], [PP-001] | [US-001] | [RC-018], [RC-019] |
| [MIL-005] | MIL | [BC-001], [PP-001] | [US-001] | [RC-020] |
| [UCD-001] | UCD | [BC-001], [SA-001] | [US-001], [UC-001] | [RC-009] | | [UCD-001] | UCD | [BC-001], [SA-001] | [US-001], [UC-001] | [RC-009] |
| [US-001] | US | [BC-001], [UCD-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004] | [UC-001] | [RC-001] | | [US-001] | US | [BC-001], [UCD-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005] | [UC-001] | [RC-001], [RC-020] |
| [UC-001] | UC | [UCD-001], [US-001], [SA-001] | [SSD-001], [DM-001] | [RC-002] | | [UC-001] | UC | [UCD-001], [US-001], [SA-001] | [SSD-001], [DM-001] | [RC-002], [RC-020] |
| [SSD-001] | SSD | [UC-001] | [OC-001] | [RC-003] | | [SSD-001] | SSD | [UC-001] | [OC-001] | [RC-003], [RC-020] |
| [DM-001] | DM | [UC-001], [SSD-001] | [DM-002], [DICT-001], [OC-001] | [RC-004] | | [DM-001] | DM | [UC-001], [SSD-001] | [DM-002], [DICT-001], [OC-001], [DCD-001] | [RC-004], [RC-020] |
| [DM-002] | DM | [DM-001] | [DICT-001] | [RC-005] | | [DM-002] | DM | [DM-001] | [DICT-001], [DCD-001], [DCD-002] | [RC-005], [RC-020] |
| [DICT-001] | DICT | [BC-001], [SA-001], [DM-001], [DM-002] | [OC-001], [SD-001] | [RC-008] | | [DICT-001] | DICT | [BC-001], [SA-001], [DM-001], [DM-002] | [OC-001], [SD-001] | [RC-008], [RC-020] |
| [OC-001] | OC | [SSD-001], [DM-001] | [SD-001] | [RC-006] | | [OC-001] | OC | [SSD-001], [DM-001] | [SD-001] | [RC-006], [RC-020] |
| [SD-001] | SD | [OC-001] | - | [RC-007] | | [SD-001] | SD | [OC-001] | [DCD-001] | [RC-007], [RC-020], [RC-021] |
| [DCD-001] | DCD | [UC-001], [DM-001], [DM-002], [OC-001], [SD-001], [DICT-001] | [DCD-002] | [RC-021] |
| [DCD-002] | DCD | [DCD-001], [DM-002], [DICT-001] | - | [RC-021] |
## Coverage Notes ## Coverage Notes
- Reviewed so far: every artifact in the project (see the Last Reviewed column). - 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. - No ERD, KPI, BMC or BPMN exists yet. `-` in Downstream means nothing is built on the artifact yet.
--- ---
@@ -55,7 +58,13 @@ updated whenever an artifact instance is created or reviewed.
[MIL-002]: ../milestones/mil-002-repositories-and-mirror.md [MIL-002]: ../milestones/mil-002-repositories-and-mirror.md
[MIL-003]: ../milestones/mil-003-scaffold-and-release.md [MIL-003]: ../milestones/mil-003-scaffold-and-release.md
[MIL-004]: ../milestones/mil-004-configurable-details.md [MIL-004]: ../milestones/mil-004-configurable-details.md
[MIL-005]: ../milestones/mil-005-credentials.md
[RC-018]: ./reviews/rc-018-mil-004.md [RC-018]: ./reviews/rc-018-mil-004.md
[RC-019]: ./reviews/rc-019-mil-004-code.md
[RC-020]: ./reviews/rc-020-mil-005.md
[RC-021]: ./reviews/rc-021-dcd.md
[DCD-001]: ../uc-001/dcd.md
[DCD-002]: ../dcd.md
[UCD-001]: ../use-case-diagram.md [UCD-001]: ../use-case-diagram.md
[US-001]: ../user-stories.md [US-001]: ../user-stories.md
[UC-001]: ../uc-001/uc.md [UC-001]: ../uc-001/uc.md
@@ -82,5 +91,5 @@ updated whenever an artifact instance is created or reviewed.
[RC-015]: ./reviews/rc-015-mil-003.md [RC-015]: ./reviews/rc-015-mil-003.md
[RC-016]: ./reviews/rc-016-create-project-sh.md [RC-016]: ./reviews/rc-016-create-project-sh.md
[RC-017]: ./reviews/rc-017-e2e-security-review.md [RC-017]: ./reviews/rc-017-e2e-security-review.md
[02875ae]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/02875aee5f2953473924074eea0056eb31af6b7a [f4d611b]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/f4d611b77cc70b4686506d44bf8f439045d9e0d2
[2a6bb8e]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/2a6bb8e8afadfe6ca4a621da30e44a372898ca62 [ded26a6]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/ded26a658c666bf29d84093cb352e3635e07719b
+340
View File
@@ -0,0 +1,340 @@
# 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 | Deprecated | Jens Tirsvad Nielsen | S02 | Initial version | [f4d611b] |
| 2026-10-06 | Accepted | Jens Tirsvad Nielsen | S02 | Added CredentialCollector, EnvFileWriter and EnvFile; writeEnvFile parameter | [ded26a6] |
---
## 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 <<controller>> {
+startProjectCreation() : PromptSet
+provideProjectDetails(name : String, description : String, visibility : Visibility, giteaOwner : Owner, githubOwner : Owner [0..1], directory : Path, enablePlanGate : Boolean, writeEnvFile : Boolean) : Summary
}
class ConfigLoader {
+load(configFile : Path, envFile : Path) : Configuration
}
class CredentialCollector {
+collect(configuration : Configuration, kinds : String [1..3]) : Configuration
}
class EnvFileWriter {
+write(project : LocalProject, configuration : Configuration, hasGithub : Boolean) : EnvFile [0..1]
}
class ToolChecker {
+check(tools : String [1..*]) : ToolCheck
}
class Preflight {
+check(request : ProjectRequest) : PreflightResult
}
abstract class GitHost <<facade>> {
-name : String
-webAddress : String
-apiAddress : String
+verifyToken() : Boolean
+ownerAccepts(owner : Owner) : Boolean
+nameFree(name : String) : Boolean
}
class GiteaClient <<facade>> {
+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 <<facade>> {
+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 EnvFile {
-address : Path
-keys : String [1..3]
}
class Template {
-name : String
-isCopied : Boolean
}
class InstallResult <<dto>>
class Summary {
-createdItems : String [0..*]
-skippedItems : String [0..*]
-nextSteps : String [0..*]
}
ProjectCreator ..> ConfigLoader : creates
ProjectCreator ..> ToolChecker : creates
ProjectCreator ..> CredentialCollector : creates
ProjectCreator ..> EnvFileWriter : creates [0..1]
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 "0..*" --> "1" Run
Run "1" *-- "1" Configuration
Run "1" *-- "1" ToolCheck
Run "1" *-- "0..1" ProjectRequest
Run "1" --> "1" PromptSet : returns
Configuration "1" *-- "1..3" Credential
ProjectRequest "0..*" --> "1" Owner : giteaOwner
ProjectRequest "0..*" --> "0..1" Owner : githubOwner
ProjectRequest "1" *-- "0..1" PreflightResult
ProjectRequest "1" --> "0..1" GiteaRepository : stored in
ProjectRequest "1" --> "0..1" GitHubRepository : also stored in
ProjectRequest "1" --> "0..1" LocalProject : working copy
Summary "0..*" --> "1" ProjectRequest : reports on
Repository <|-- GiteaRepository
Repository <|-- GitHubRepository
Repository "0..*" --> "1" Owner : owned by
GiteaRepository "1" *-- "0..1" LicenseFile
PushMirror "0..*" --> "1" GiteaRepository : source
PushMirror "0..*" --> "1" GitHubRepository : target
PushMirror "0..*" --> "1" Credential : authorised by
LocalProject "1" *-- "1..2" Remote
Remote "0..*" --> "1" Repository : points to
LocalProject "1" *-- "1" Submodule
LocalProject "1" *-- "1" HookSetup
LocalProject "1" *-- "0..*" Template
LocalProject "1" *-- "0..1" EnvFile
EnvFile "0..*" --> "1..3" Credential : copy of
InstallResult "0..*" --> "1" Submodule
InstallResult "0..*" --> "1" HookSetup
InstallResult "0..*" --> "0..*" Template
ProjectRequest "0..*" --> "1" Visibility
Repository "0..*" --> "1" 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` |
| `CredentialCollector` | none (system concept) | Asks, without echo, for a credential that `.env` does not provide and validates it like one read from `.env`. | none | `collect` |
| `EnvFileWriter` | Credentials File | Creates the project's own `.env` with the credentials the project needs: owner-only, excluded from git, never replaced without a yes. | none | `write` |
| `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 |
| `EnvFile` | Credentials File | The `.env` of the project: a copy of the credentials it needs. | `address`, `keys` | 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, writeEnvFile) : Summary` | [OC-001] `provideProjectDetails`; [SD-001] `provideProjectDetails(...)` |
| `ConfigLoader.load(configFile, envFile) : Configuration` | [SD-001] `load(config.env, .env)`; [OC-001] `startProjectCreation` P2 |
| `CredentialCollector.collect(configuration, kinds) : Configuration` | [SD-001] `collect(configuration, GITEA_TOKEN)` and `collect(configuration, GITHUB_PAT, GITHUB_USER)`; [OC-001] `startProjectCreation` P2 and the precondition of `provideProjectDetails` |
| `EnvFileWriter.write(project, configuration, hasGithub) : EnvFile` | [SD-001] `write(localProject, configuration, githubOwner present)`; [OC-001] `provideProjectDetails` P14 |
| `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`, `CredentialCollector`, `EnvFileWriter`, `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. `CredentialCollector` and `EnvFileWriter` depend only on `Configuration`, `Credential` and `LocalProject`; the only class that holds a secret after the run is `EnvFile`, and only as a copy written to the Maintainer's own disk. `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` |
| `CredentialCollector` | planned for [MIL-005]: `lib/credentials.sh`, with `lib/prompts.sh` |
| `EnvFileWriter` | planned for [MIL-005]: `lib/envfile.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
[MIL-005]: ../milestones/mil-005-credentials.md
[DICT-001]: ../dictionary.md
[DCD-002]: ../dcd.md
[f4d611b]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/f4d611b77cc70b4686506d44bf8f439045d9e0d2
[ded26a6]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/ded26a658c666bf29d84093cb352e3635e07719b
+11 -3
View File
@@ -9,8 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-05 | Accepted | Jens Tirsvad Nielsen | S02 | Initial version | [02875ae] | | 2026-10-05 | Deprecated | Jens Tirsvad Nielsen | S02 | Configuration may hold preset project details | [2a6bb8e] |
| 2026-10-05 | Accepted | Jens Tirsvad Nielsen | S02 | Configuration may hold preset project details | [2a6bb8e] | | 2026-10-06 | Accepted | Jens Tirsvad Nielsen | S02 | Added Credentials File (a Local Project may have one) | [ded26a6] |
--- ---
@@ -79,6 +79,9 @@ class "Framework Setup" as FrameworkSetup {
class Template { class Template {
name name
} }
class "Credentials File" as CredentialsFile {
address
}
class Summary { class Summary {
created items created items
skipped items skipped items
@@ -108,6 +111,8 @@ LocalProject "1" --> "1" FrameworkSetup : has
FrameworkSetup "0..*" --> "1" Framework : is installed from FrameworkSetup "0..*" --> "1" Framework : is installed from
Framework "1" --> "1..*" Template : provides Framework "1" --> "1..*" Template : provides
LocalProject "1" --> "0..*" Template : contains a copy of LocalProject "1" --> "0..*" Template : contains a copy of
LocalProject "1" --> "0..1" CredentialsFile : has
CredentialsFile "1" --> "1..2" AccessToken : holds a copy of
Summary "1" --> "1" Project : reports on Summary "1" --> "1" Project : reports on
@enduml @enduml
``` ```
@@ -132,6 +137,7 @@ Summary "1" --> "1" Project : reports on
| Framework | The SQA-QC-Framework added to a Local Project | name, address | [UC-001] step 9 "framework submodule" | | Framework | The SQA-QC-Framework added to a Local Project | name, address | [UC-001] step 9 "framework submodule" |
| Framework Setup | The skills and git hooks installed from the Framework, with the plan gate on or off | plan gate enabled | [UC-001] step 9 "skills and hooks", "plan gate" | | Framework Setup | The skills and git hooks installed from the Framework, with the plan gate on or off | plan gate enabled | [UC-001] step 9 "skills and hooks", "plan gate" |
| Template | A file the Framework provides to copy into a project (`AGENTS.md`, artifact registry) | name | [UC-001] step 9 "templates" | | Template | A file the Framework provides to copy into a project (`AGENTS.md`, artifact registry) | name | [UC-001] step 9 "templates" |
| Credentials File | The file in a Local Project that holds a copy of the Access Tokens (and the GitHub account name) the project needs; readable by its owner only and ignored by git | address | [UC-001] step 9 "credentials file" |
| Summary | The report of what was created, skipped or failed and how to continue | created items, skipped items, next steps | [UC-001] step 10 "summary" | | Summary | The report of what was created, skipped or failed and how to continue | created items, skipped items, next steps | [UC-001] step 10 "summary" |
## Association Table ## Association Table
@@ -158,6 +164,8 @@ Summary "1" --> "1" Project : reports on
| Framework Setup | is installed from | Framework | 0..* to 1 | | Framework Setup | is installed from | Framework | 0..* to 1 |
| Framework | provides | Template | 1 to 1..* | | Framework | provides | Template | 1 to 1..* |
| Local Project | contains a copy of | Template | 1 to 0..* | | Local Project | contains a copy of | Template | 1 to 0..* |
| Local Project | has | Credentials File | 1 to 0..1 |
| Credentials File | holds a copy of | Access Token | 1 to 1..2 |
| Summary | reports on | Project | 1 to 1 | | Summary | reports on | Project | 1 to 1 |
## Generalizations ## Generalizations
@@ -172,5 +180,5 @@ Summary "1" --> "1" Project : reports on
[SSD-001]: ./ssd.md [SSD-001]: ./ssd.md
[DICT-001]: ../dictionary.md [DICT-001]: ../dictionary.md
[DM-002]: ../domain-model.md [DM-002]: ../domain-model.md
[02875ae]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/02875aee5f2953473924074eea0056eb31af6b7a
[2a6bb8e]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/2a6bb8e8afadfe6ca4a621da30e44a372898ca62 [2a6bb8e]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/2a6bb8e8afadfe6ca4a621da30e44a372898ca62
[ded26a6]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/ded26a658c666bf29d84093cb352e3635e07719b
+11 -7
View File
@@ -9,8 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-05 | Accepted | Jens Tirsvad Nielsen | S02 | Initial version | [02875ae] | | 2026-10-05 | Deprecated | Jens Tirsvad Nielsen | S02 | Project details may be defined by the Configuration | [2a6bb8e] |
| 2026-10-05 | Accepted | Jens Tirsvad Nielsen | S02 | Project details may be defined by the Configuration | [2a6bb8e] | | 2026-10-06 | Accepted | Jens Tirsvad Nielsen | S02 | Credentials asked when missing; EnvFile created in the local project (P14); writeEnvFile parameter | [ded26a6] |
--- ---
@@ -31,7 +31,7 @@ Concepts below use the IT terms of [DICT-001] for the PO concepts of [DM-001]. `
**Postconditions** **Postconditions**
- P1. A `Run` instance was created. - P1. A `Run` instance was created.
- P2. A `Configuration` instance was created from `config.env` and `.env`, with every value validated (including the project details preset in `config.env`) and the credentials held only in memory. - P2. A `Configuration` instance was created from `config.env` and `.env`, with every value validated (including the project details preset in `config.env`). A `Credential` that `.env` did not provide was entered by the Maintainer without echo and validated; every `Credential` is held only in memory.
- P3. A `ToolCheck` instance was created and associated with the `Run`, recording that `git` and `curl` are present and whether `jq` is present. - P3. A `ToolCheck` instance was created and associated with the `Run`, recording that `git` and `curl` are present and whether `jq` is present.
- P4. The `Run` was associated with a `PromptSet` that is returned. - P4. The `Run` was associated with a `PromptSet` that is returned.
@@ -39,21 +39,23 @@ Concepts below use the IT terms of [DICT-001] for the PO concepts of [DM-001]. `
| Condition (failing precondition) | Outcome | | Condition (failing precondition) | Outcome |
| --- | --- | | --- | --- |
| `config.env` or `.env` is missing, or a value is missing or malformed (a preset project detail included) | The `Run` ends with an error naming the key, never its value; nothing was changed | | `config.env` is missing, or a value in `config.env` or `.env` is malformed (a preset project detail included) | The `Run` ends with an error naming the key, never its value; nothing was changed |
| A `Credential` is missing and input ends before a valid one is entered | The `Run` ends with an error naming the key; nothing was changed |
| `git` or `curl` is missing | The `Run` ends with an error naming the tool; nothing was changed | | `git` or `curl` is missing | The `Run` ends with an error naming the tool; nothing was changed |
## Contract: provideProjectDetails ## Contract: provideProjectDetails
| Item | Value | | Item | Value |
| --- | --- | | --- | --- |
| Operation | `provideProjectDetails(name: String, description: String, visibility: Visibility, giteaOwner: Owner, githubOwner: Owner [0..1], directory: Path, enablePlanGate: Boolean): Summary` | | Operation | `provideProjectDetails(name: String, description: String, visibility: Visibility, giteaOwner: Owner, githubOwner: Owner [0..1], directory: Path, enablePlanGate: Boolean, writeEnvFile: Boolean): Summary` |
| Traces to | `provideProjectDetails` in [SSD-001] | | Traces to | `provideProjectDetails` in [SSD-001] |
| Concepts | ProjectRequest, PreflightResult, GiteaRepository, GitHubRepository, LicenseFile, PushMirror, LocalProject, Remote, Submodule, HookSetup, Summary | | Concepts | ProjectRequest, PreflightResult, GiteaRepository, GitHubRepository, LicenseFile, PushMirror, LocalProject, Remote, Submodule, HookSetup, EnvFile, Summary |
**Preconditions** **Preconditions**
- A `Run` exists and its `Configuration` is valid (from `startProjectCreation`). - A `Run` exists and its `Configuration` is valid (from `startProjectCreation`).
- `githubOwner` is present exactly when the Maintainer chose GitHub. - `githubOwner` is present exactly when the Maintainer chose GitHub.
- When `githubOwner` is present, the GitHub `Credential`s are known: from `.env`, or entered by the Maintainer without echo and validated before the first request.
- A detail that the `Configuration` defines is not asked: it is taken from the `Configuration`. - A detail that the `Configuration` defines is not asked: it is taken from the `Configuration`.
**Postconditions** **Postconditions**
@@ -71,6 +73,7 @@ Concepts below use the IT terms of [DICT-001] for the PO concepts of [DM-001]. `
- P11. A `HookSetup` instance was associated with the `LocalProject`, recording that skills and git hooks were installed once and, if `enablePlanGate`, that the plan gate was enabled. - P11. A `HookSetup` instance was associated with the `LocalProject`, recording that skills and git hooks were installed once and, if `enablePlanGate`, that the plan gate was enabled.
- P12. `AGENTS.md` and `docs/artifact-registry.md` exist in the `LocalProject`, each either newly copied from the framework templates or left as it was because the Maintainer declined to replace it. - P12. `AGENTS.md` and `docs/artifact-registry.md` exist in the `LocalProject`, each either newly copied from the framework templates or left as it was because the Maintainer declined to replace it.
- P13. A `Summary` instance was created listing every created item, every skipped item and the next step for anything that failed, and is returned. It contains no credential. - P13. A `Summary` instance was created listing every created item, every skipped item and the next step for anything that failed, and is returned. It contains no credential.
- P14. If `writeEnvFile`, an `EnvFile` named `.env` was associated with the `LocalProject`, holding only the `Credential`s the project needs (the Gitea token, and the GitHub token and account name when `githubOwner` is present). It is readable by its owner only and excluded from git without a change to any tracked file, and no `Credential` is shown in any output. If `writeEnvFile` is false, no `EnvFile` was created. An existing `.env` is left as it was unless the Maintainer agreed to replace it.
**Exceptions** **Exceptions**
@@ -81,6 +84,7 @@ Concepts below use the IT terms of [DICT-001] for the PO concepts of [DM-001]. `
| `GiteaRepository` creation fails after a `GitHubRepository` was created (P5, P3 ordering) | The `Summary` lists the `GitHubRepository` as created, the `GiteaRepository` as failed and how to continue | | `GiteaRepository` creation fails after a `GitHubRepository` was created (P5, P3 ordering) | The `Summary` lists the `GitHubRepository` as created, the `GiteaRepository` as failed and how to continue |
| `PushMirror` creation fails (P6) | The `Summary` lists both repositories as created, the mirror as failed and how to continue; the local steps are not run | | `PushMirror` creation fails (P6) | The `Summary` lists both repositories as created, the mirror as failed and how to continue; the local steps are not run |
| `directory` exists, or a target file exists, and the Maintainer declines replacing it (P7, P12) | That item is skipped and listed in the `Summary` | | `directory` exists, or a target file exists, and the Maintainer declines replacing it (P7, P12) | That item is skipped and listed in the `Summary` |
| A `.env` already exists in the `LocalProject` and the Maintainer declines replacing it (P14) | That item is skipped and listed in the `Summary` |
| A different `core.hooksPath` exists and the Maintainer declines replacing it (P11) | Hooks are not installed and this is listed in the `Summary` | | A different `core.hooksPath` exists and the Maintainer declines replacing it (P11) | Hooks are not installed and this is listed in the `Summary` |
| SSH to port 10022 fails and the `Submodule` cannot be added (P10) | The `Summary` lists the repositories as created, the submodule as failed, and the SSH prerequisite | | SSH to port 10022 fails and the `Submodule` cannot be added (P10) | The `Summary` lists the repositories as created, the submodule as failed, and the SSH prerequisite |
@@ -90,5 +94,5 @@ Concepts below use the IT terms of [DICT-001] for the PO concepts of [DM-001]. `
[DM-001]: ./dm.md [DM-001]: ./dm.md
[DICT-001]: ../dictionary.md [DICT-001]: ../dictionary.md
[SD-001]: ./sd.md [SD-001]: ./sd.md
[02875ae]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/02875aee5f2953473924074eea0056eb31af6b7a
[2a6bb8e]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/2a6bb8e8afadfe6ca4a621da30e44a372898ca62 [2a6bb8e]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/2a6bb8e8afadfe6ca4a621da30e44a372898ca62
[ded26a6]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/ded26a658c666bf29d84093cb352e3635e07719b
+51 -22
View File
@@ -4,17 +4,17 @@
| Key | Value | | Key | Value |
| --- | --- | | --- | --- |
| ID | SD-001 | | ID | SD-001 |
| CrossReference | [OC-001] | | CrossReference | [OC-001], [DCD-001] |
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-05 | Accepted | Jens Tirsvad Nielsen | S02 | Initial version | [02875ae] | | 2026-10-06 | Deprecated | Jens Tirsvad Nielsen | S02 | Messages aligned with the method signatures of DCD-001<br>Cited DCD-001 | [f4d611b] |
| 2026-10-05 | Accepted | Jens Tirsvad Nielsen | S02 | Note: preset project details are read by ConfigLoader | [2a6bb8e] | | 2026-10-06 | Accepted | Jens Tirsvad Nielsen | S02 | Added CredentialCollector and EnvFileWriter and their messages (P2, P14) | [ded26a6] |
--- ---
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 ## Sequence: startProjectCreation
@@ -28,6 +28,7 @@ actor Maintainer
participant ":ProjectCreator" as PC participant ":ProjectCreator" as PC
participant ":ConfigLoader" as CL participant ":ConfigLoader" as CL
participant ":ToolChecker" as TC participant ":ToolChecker" as TC
participant ":CredentialCollector" as CC
Maintainer -> PC : startProjectCreation() Maintainer -> PC : startProjectCreation()
activate PC activate PC
@@ -36,6 +37,11 @@ PC -> CL : load(config.env, .env)
activate CL activate CL
CL --> PC : configuration CL --> PC : configuration
deactivate CL deactivate CL
create CC
PC -> CC : collect(configuration, GITEA_TOKEN)
activate CC
CC --> PC : configuration
deactivate CC
create TC create TC
PC -> TC : check(git, curl, jq) PC -> TC : check(git, curl, jq)
activate TC activate TC
@@ -44,6 +50,7 @@ deactivate TC
PC --> Maintainer : promptSet PC --> Maintainer : promptSet
deactivate PC deactivate PC
destroy CL destroy CL
destroy CC
destroy TC destroy TC
@enduml @enduml
``` ```
@@ -53,7 +60,7 @@ destroy TC
| Pattern (GRASP / GoF) | Applied to | Rationale | | Pattern (GRASP / GoF) | Applied to | Rationale |
| --- | --- | --- | | --- | --- | --- |
| Controller (GRASP) | `ProjectCreator` | Receives the system operations and coordinates, without doing the work itself | | Controller (GRASP) | `ProjectCreator` | Receives the system operations and coordinates, without doing the work itself |
| Pure Fabrication (GRASP) | `ConfigLoader`, `ToolChecker` | No domain concept owns parsing or tool checks; separate small units keep cohesion high | | Pure Fabrication (GRASP) | `ConfigLoader`, `ToolChecker`, `CredentialCollector` | No domain concept owns parsing, tool checks or asking for a credential; separate small units keep cohesion high |
| Creator (GRASP) | `ConfigLoader` creates `Configuration` | It holds the data needed to build and validate it | | Creator (GRASP) | `ConfigLoader` creates `Configuration` | It holds the data needed to build and validate it |
### Postcondition Coverage ### Postcondition Coverage
@@ -61,13 +68,13 @@ destroy TC
| Postcondition (from contract) | Satisfied by message | | Postcondition (from contract) | Satisfied by message |
| --- | --- | | --- | --- |
| P1 Run created | `startProjectCreation` received by `ProjectCreator` | | P1 Run created | `startProjectCreation` received by `ProjectCreator` |
| P2 Configuration created and validated | `load(config.env, .env)` | | P2 Configuration created and validated; a missing credential entered, validated and held in memory | `load(config.env, .env)` and `collect(configuration, GITEA_TOKEN)` |
| P3 ToolCheck created | `check(git, curl, jq)` | | P3 ToolCheck created | `check(git, curl, jq)` |
| P4 PromptSet returned | `promptSet` return to the Maintainer | | P4 PromptSet returned | `promptSet` return to the Maintainer |
### Responsibility Check ### Responsibility Check
`ProjectCreator` only sequences two calls; parsing and validation sit in `ConfigLoader`, tool detection in `ToolChecker`. No object receives every message. Project details preset in `config.env` are read and validated by `ConfigLoader` as part of `configuration`; the second sequence is unchanged, because `provideProjectDetails` receives the same arguments whether they were asked or preset. `ProjectCreator` only sequences three calls; parsing and validation sit in `ConfigLoader`, asking for a missing credential in `CredentialCollector`, tool detection in `ToolChecker`. No object receives every message. Project details preset in `config.env` are read and validated by `ConfigLoader` as part of `configuration`; the second sequence is unchanged, because `provideProjectDetails` receives the same arguments whether they were asked or preset.
## Sequence: provideProjectDetails ## Sequence: provideProjectDetails
@@ -85,8 +92,10 @@ participant ":GitHubClient" as GH
participant ":LocalProjectBuilder" as LB participant ":LocalProjectBuilder" as LB
participant ":FrameworkInstaller" as FI participant ":FrameworkInstaller" as FI
participant ":SummaryReport" as SR participant ":SummaryReport" as SR
participant ":CredentialCollector" as CC
participant ":EnvFileWriter" as EW
Maintainer -> PC : provideProjectDetails(name, description, visibility, giteaOwner, githubOwner, directory, enablePlanGate) Maintainer -> PC : provideProjectDetails(name, description, visibility, giteaOwner, githubOwner, directory, enablePlanGate, writeEnvFile)
activate PC activate PC
create GT create GT
PC -> GT : new(configuration) PC -> GT : new(configuration)
@@ -94,6 +103,13 @@ opt githubOwner present
create GH create GH
PC -> GH : new(configuration) PC -> GH : new(configuration)
end end
opt githubOwner present
create CC
PC -> CC : collect(configuration, GITHUB_PAT, GITHUB_USER)
activate CC
CC --> PC : configuration
deactivate CC
end
create PF create PF
PC -> PF : check(request) PC -> PF : check(request)
activate PF activate PF
@@ -105,16 +121,16 @@ PF --> PC : preflightResult
deactivate PF deactivate PF
opt githubOwner present opt githubOwner present
PC -> GH : createEmptyRepository(githubOwner, name) PC -> GH : createEmptyRepository(request)
activate GH activate GH
GH --> PC : gitHubRepository GH --> PC : gitHubRepository
deactivate GH deactivate GH
end end
alt githubOwner present alt githubOwner present
PC -> GT : createRepository(giteaOwner, name, license=AGPL-3.0) PC -> GT : createRepository(request, license=AGPL-3.0)
else no GitHub else no GitHub
PC -> GT : createRepository(giteaOwner, name, license=none) PC -> GT : createRepository(request, license=none)
end end
activate GT activate GT
GT --> PC : giteaRepository GT --> PC : giteaRepository
@@ -123,7 +139,7 @@ deactivate GT
opt githubOwner present opt githubOwner present
PC -> GT : addPushMirror(giteaRepository, gitHubRepository) PC -> GT : addPushMirror(giteaRepository, gitHubRepository)
activate GT activate GT
GT -> GT : requestSync() GT -> GT : requestSync(pushMirror)
GT --> PC : pushMirror GT --> PC : pushMirror
deactivate GT deactivate GT
end end
@@ -137,11 +153,19 @@ deactivate LB
create FI create FI
PC -> FI : install(localProject, enablePlanGate) PC -> FI : install(localProject, enablePlanGate)
activate FI activate FI
FI --> PC : submodule, hookSetup, templates FI --> PC : installResult
deactivate FI deactivate FI
opt writeEnvFile
create EW
PC -> EW : write(localProject, configuration, githubOwner present)
activate EW
EW --> PC : envFile
deactivate EW
end
create SR create SR
PC -> SR : compose(all results) PC -> SR : compose(request)
SR --> PC : summary SR --> PC : summary
PC --> Maintainer : summary PC --> Maintainer : summary
deactivate PC deactivate PC
@@ -150,6 +174,8 @@ destroy GT
destroy GH destroy GH
destroy LB destroy LB
destroy FI destroy FI
destroy CC
destroy EW
destroy SR destroy SR
@enduml @enduml
``` ```
@@ -159,7 +185,7 @@ destroy SR
| Pattern (GRASP / GoF) | Applied to | Rationale | | Pattern (GRASP / GoF) | Applied to | Rationale |
| --- | --- | --- | | --- | --- | --- |
| Controller (GRASP) | `ProjectCreator` | Single entry for the system operation; sequences the steps and stops on the first failure | | Controller (GRASP) | `ProjectCreator` | Single entry for the system operation; sequences the steps and stops on the first failure |
| Pure Fabrication (GRASP) | `Preflight`, `LocalProjectBuilder`, `FrameworkInstaller`, `SummaryReport` | Each groups one responsibility that no domain concept owns | | Pure Fabrication (GRASP) | `Preflight`, `LocalProjectBuilder`, `FrameworkInstaller`, `SummaryReport`, `CredentialCollector`, `EnvFileWriter` | Each groups one responsibility that no domain concept owns |
| Facade (GoF) | `GiteaClient`, `GitHubClient` | Hide each host's HTTP API and credential handling behind a small interface; tokens never leave them | | Facade (GoF) | `GiteaClient`, `GitHubClient` | Hide each host's HTTP API and credential handling behind a small interface; tokens never leave them |
| Protection from variations (GRASP) | Client classes | The `github`-optional and license variations are decided by the controller's `alt` and `opt`, not inside the clients | | Protection from variations (GRASP) | Client classes | The `github`-optional and license variations are decided by the controller's `alt` and `opt`, not inside the clients |
@@ -169,24 +195,27 @@ destroy SR
| --- | --- | | --- | --- |
| P1 ProjectRequest created | `provideProjectDetails` received by `ProjectCreator` | | P1 ProjectRequest created | `provideProjectDetails` received by `ProjectCreator` |
| P2 PreflightResult created | `check(request)` | | 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` | | 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)` | | P2 GitHub credentials known before the first request | `collect(configuration, GITHUB_PAT, GITHUB_USER)` inside `opt githubOwner present` |
| 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, ...)` | | P7 LocalProject created, history from Gitea when not empty | `build(directory, ...)` |
| P8 origin remote (SSH if the test passed, else HTTPS) | `build(..., sshPassed)` | | P8 origin remote (SSH if the test passed, else HTTPS) | `build(..., sshPassed)` |
| P9 github remote when chosen | `build(...)` | | P9 github remote when chosen | `build(...)` |
| P10 framework Submodule | `install(localProject, ...)` | | P10 framework Submodule | `install(localProject, ...)` |
| P11 HookSetup, plan gate if chosen | `install(localProject, enablePlanGate)` | | P11 HookSetup, plan gate if chosen | `install(localProject, enablePlanGate)` |
| P12 AGENTS.md and registry copied or kept | `install(...)` returning `templates` | | 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 |
| P14 EnvFile created with the needed credentials, owner-only, ignored by git, or none when declined | `write(localProject, configuration, githubOwner present)` inside `opt writeEnvFile` |
### Responsibility Check ### Responsibility Check
`ProjectCreator` sequences and decides on the optional paths but performs no HTTP, git or file work. Host calls are in the two clients, local work in `LocalProjectBuilder` and `FrameworkInstaller`, reporting in `SummaryReport`, so cohesion stays high and no object receives all messages. Failure handling (exceptions in [OC-001]) is the controller's single stop-and-report rule and is not drawn. `ProjectCreator` sequences and decides on the optional paths but performs no HTTP, git or file work. Host calls are in the two clients, local work in `LocalProjectBuilder` and `FrameworkInstaller`, the credential prompts in `CredentialCollector`, the `.env` in `EnvFileWriter`, reporting in `SummaryReport`, so cohesion stays high and no object receives all messages. Failure handling (exceptions in [OC-001]) is the controller's single stop-and-report rule and is not drawn.
--- ---
[OC-001]: ./oc.md [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 [f4d611b]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/f4d611b77cc70b4686506d44bf8f439045d9e0d2
[ded26a6]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/ded26a658c666bf29d84093cb352e3635e07719b
+7 -7
View File
@@ -9,8 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-05 | Accepted | Jens Tirsvad Nielsen | S02 | Optional GitHub; choosing GitHub applies the AGPL license to the Gitea repository<br>Cited OC-001 and DM-001 | [02875ae] | | 2026-10-05 | Deprecated | Jens Tirsvad Nielsen | S02 | Parameters may come from config.env; the message is unchanged | [2a6bb8e] |
| 2026-10-05 | Accepted | Jens Tirsvad Nielsen | S02 | Parameters may come from config.env; the message is unchanged | [2a6bb8e] | | 2026-10-06 | Accepted | Jens Tirsvad Nielsen | S02 | writeEnvFile parameter and the credentials that .env does not provide | [ded26a6] |
--- ---
@@ -26,7 +26,7 @@ actor Maintainer as A
participant ":System" as S participant ":System" as S
A -> S : startProjectCreation() A -> S : startProjectCreation()
S --> A : prompts for project details S --> A : prompts for project details
A -> S : provideProjectDetails(name, description, visibility, giteaOwner, githubOwner, directory, enablePlanGate) A -> S : provideProjectDetails(name, description, visibility, giteaOwner, githubOwner, directory, enablePlanGate, writeEnvFile)
S --> A : checks passed S --> A : checks passed
S --> A : creation summary S --> A : creation summary
@enduml @enduml
@@ -36,19 +36,19 @@ S --> A : creation summary
| Step | Message | Parameters | Return | Use case step | | Step | Message | Parameters | Return | Use case step |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| 1 | startProjectCreation | none | prompts for project details (after configuration and tool checks) | 1, 2 | | 1 | startProjectCreation | none | prompts for project details (after configuration and tool checks; a missing credential is asked first) | 1, 2 |
| 2 | provideProjectDetails | name, description, visibility, giteaOwner, githubOwner (optional; given means GitHub is chosen and the Gitea repository gets the AGPL license; omitted means no GitHub and no license), directory, enablePlanGate (each of these may come from `config.env` instead of the Maintainer; the message is unchanged) | checks passed, then a creation summary | 3 to 10 | | 2 | provideProjectDetails | name, description, visibility, giteaOwner, githubOwner (optional; given means GitHub is chosen and the Gitea repository gets the AGPL license; omitted means no GitHub and no license), directory, enablePlanGate (each of these may come from `config.env` instead of the Maintainer; the message is unchanged), writeEnvFile (whether to create the project's `.env`), and the credentials that `.env` does not provide (GitHub ones only when GitHub is chosen) | checks passed, then a creation summary | 3 to 10 |
Steps 4 to 9 are internal to the system, so one operation covers them. A consent question (step 8a, 9a, 9b) is a prompt from the system and is out of scope for this diagram; failure flows are out of scope here. Steps 4 to 9 are internal to the system, so one operation covers them. A consent question (step 8a, 9a, 9b) is a prompt from the system and is out of scope for this diagram; failure flows are out of scope here.
## Lifecycle Notes ## Lifecycle Notes
The system is one script run. It starts with the first operation and ends after the summary; nothing persists between runs. The system is one script run. It starts with the first operation and ends after the summary; nothing persists between runs except the `.env` the Maintainer agreed to in the new project.
--- ---
[UC-001]: ./uc.md [UC-001]: ./uc.md
[DM-001]: ./dm.md [DM-001]: ./dm.md
[OC-001]: ./oc.md [OC-001]: ./oc.md
[02875ae]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/02875aee5f2953473924074eea0056eb31af6b7a
[2a6bb8e]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/2a6bb8e8afadfe6ca4a621da30e44a372898ca62 [2a6bb8e]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/2a6bb8e8afadfe6ca4a621da30e44a372898ca62
[ded26a6]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/ded26a658c666bf29d84093cb352e3635e07719b
+16 -7
View File
@@ -9,8 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-05 | Accepted | Jens Tirsvad Nielsen | S02 | Optional GitHub; choosing GitHub applies the AGPL license to the Gitea repository<br>Cited DM-001 and UCD-001 | [02875ae] | | 2026-10-05 | Deprecated | Jens Tirsvad Nielsen | S02 | Step 3: details set in config.env are not asked (extensions 3a, 3b) | [2a6bb8e] |
| 2026-10-05 | Accepted | Jens Tirsvad Nielsen | S02 | Step 3: details set in config.env are not asked (extensions 3a, 3b) | [2a6bb8e] | | 2026-10-06 | Accepted | Jens Tirsvad Nielsen | S02 | Credentials not in .env are asked (step 2, extension 2b); the project .env is created with consent (step 9, extensions 9c, 9d) | [ded26a6] |
--- ---
@@ -26,33 +26,36 @@
- S02 — credentials are never exposed and nothing is overwritten silently - S02 — credentials are never exposed and nothing is overwritten silently
- S03 — the published procedure is documented and reusable - S03 — the published procedure is documented and reusable
- **Preconditions:** - **Preconditions:**
- `config.env` and `.env` exist and are valid. - `config.env` exists and is valid. `.env` may be missing or hold only some credentials; a credential it does not provide is asked.
- `config.env` may preset any of the project details of step 3. - `config.env` may preset any of the project details of step 3.
- `git` and `curl` are installed. - `git` and `curl` are installed.
- The Maintainer has a Gitea token, a GitHub PAT (only when GitHub is chosen) and SSH access to Gitea on port 10022. - The Maintainer has a Gitea token, a GitHub PAT and a GitHub account name (only when GitHub is chosen) and SSH access to Gitea on port 10022.
- **Postconditions (success guarantee):** - **Postconditions (success guarantee):**
- A repository exists on Gitea under the chosen owner. It is empty, or, when the Maintainer chose GitHub, it holds the AGPL license file. - A repository exists on Gitea under the chosen owner. It is empty, or, when the Maintainer chose GitHub, it holds the AGPL license file.
- When the Maintainer chose to create a GitHub repository, an empty repository exists on GitHub under the chosen owner, the Gitea repository is a push mirror to it, and the AGPL license file reaches GitHub through the mirror. - When the Maintainer chose to create a GitHub repository, an empty repository exists on GitHub under the chosen owner, the Gitea repository is a push mirror to it, and the AGPL license file reaches GitHub through the mirror.
- A local project directory exists with credential-free remotes `origin` (Gitea) and, when GitHub was chosen, `github`, the `framework` submodule, installed skills and hooks, and the copied templates. - A local project directory exists with credential-free remotes `origin` (Gitea) and, when GitHub was chosen, `github`, the `framework` submodule, installed skills and hooks, and the copied templates.
- When the Maintainer agreed, the local project has a `.env` that holds only the credentials the project needs, is readable by its owner only and is ignored by git.
- The Maintainer has a summary of what was created. - The Maintainer has a summary of what was created.
### Main Success Scenario ### Main Success Scenario
1. The Maintainer starts the project creation. 1. The Maintainer starts the project creation.
2. The system loads and validates the configuration and credentials and checks that the required tools exist. 2. The system loads and validates the configuration and credentials and checks that the required tools exist. A credential that `.env` does not provide is asked, without echo; the GitHub credentials are asked once GitHub is chosen.
3. The Maintainer provides the repository name, description, visibility, the Gitea owner, whether to also create a GitHub repository (and if so its owner), the local directory, and whether to enable the plan gate. A detail that is set in `config.env` is not asked. 3. The Maintainer provides the repository name, description, visibility, the Gitea owner, whether to also create a GitHub repository (and if so its owner), the local directory, and whether to enable the plan gate. A detail that is set in `config.env` is not asked.
4. The system checks that the tokens needed for the chosen hosts work, that the owners accept new repositories, that the name is free on those hosts, and whether SSH to Gitea works. 4. The system checks that the tokens needed for the chosen hosts work, that the owners accept new repositories, that the name is free on those hosts, and whether SSH to Gitea works.
5. Optional: if the Maintainer chose GitHub, the system creates the empty GitHub repository. 5. Optional: if the Maintainer chose GitHub, the system creates the empty GitHub repository.
6. The system creates the Gitea repository. If the Maintainer chose GitHub, the repository is created with the AGPL license file and so is not empty; otherwise it is empty and has no license. 6. The system creates the Gitea repository. If the Maintainer chose GitHub, the repository is created with the AGPL license file and so is not empty; otherwise it is empty and has no license.
7. Optional: if GitHub was chosen, the system configures the Gitea repository as a push mirror to GitHub and verifies it. A license file in the Gitea repository is pushed to GitHub by the mirror. 7. Optional: if GitHub was chosen, the system configures the Gitea repository as a push mirror to GitHub and verifies it. A license file in the Gitea repository is pushed to GitHub by the mirror.
8. The system creates the local project with the `origin` remote and, if GitHub was chosen, the `github` remote. 8. The system creates the local project with the `origin` remote and, if GitHub was chosen, the `github` remote.
9. The system adds the framework submodule, installs its skills and hooks (and the plan gate if chosen) and copies the templates. 9. The system adds the framework submodule, installs its skills and hooks (and the plan gate if chosen) and copies the templates. If the Maintainer agrees, it also creates the project's own `.env` with the credentials the project needs.
10. The system reports a summary of what was created. 10. The system reports a summary of what was created.
### Extensions (Alternative / Exception Flows) ### Extensions (Alternative / Exception Flows)
- 2a. A required tool is missing, or a configuration value is missing or malformed: - 2a. A required tool is missing, or a configuration value is missing or malformed:
1. The system stops before any change and names the problem without showing a credential. 1. The system stops before any change and names the problem without showing a credential.
- 2b. A credential is not provided in `.env`:
1. The system asks for it without showing what is typed. An invalid value is refused and asked again; when input ends the system stops before any change and names the key.
- 3a. A project detail is set in `config.env`: - 3a. A project detail is set in `config.env`:
1. The system uses it and does not ask for it; the summary says it came from the configuration. 1. The system uses it and does not ask for it; the summary says it came from the configuration.
- 3b. A configured project detail is invalid: - 3b. A configured project detail is invalid:
@@ -69,12 +72,18 @@
1. The system asks the Maintainer before replacing it; on no, it skips that item and reports it. 1. The system asks the Maintainer before replacing it; on no, it skips that item and reports it.
- 9b. A different git hooks setup is already configured in the project: - 9b. A different git hooks setup is already configured in the project:
1. The system asks before replacing it. 1. The system asks before replacing it.
- 9c. The Maintainer declines creating the project's `.env`:
1. The system creates none and says so in the summary.
- 9d. A `.env` already exists in the project:
1. The system asks before replacing it; on no, it keeps it and reports it.
### Special Requirements / Business Rules ### Special Requirements / Business Rules
| Step | Rule | | Step | Rule |
| --- | --- | | --- | --- |
| 2, 4 | A token never appears in output, logs, command lines, remote URLs or temporary files left behind | | 2, 4 | A token never appears in output, logs, command lines, remote URLs or temporary files left behind |
| 2 | A credential that is asked is read without echo, validated like one read from `.env`, and held in memory for the run |
| 9 | The project's `.env` is the only place a token is written. It is created only after a yes (default no), holds only the keys the project needs (`GITEA_TOKEN`; `GITHUB_PAT` and `GITHUB_USER` when GitHub was chosen), is readable by its owner only, is excluded from git without changing a tracked file, and is never replaced without a yes |
| 3 | The GitHub owner and the Gitea owner are chosen separately; `GITHUB_USER` is only the authenticating account | | 3 | The GitHub owner and the Gitea owner are chosen separately; `GITHUB_USER` is only the authenticating account |
| 3 | A project detail set in `config.env` (the key is present, even if empty where an empty value is allowed) is not asked; only the confirmations stay interactive | | 3 | A project detail set in `config.env` (the key is present, even if empty where an empty value is allowed) is not asked; only the confirmations stay interactive |
| 3, 5, 7 | GitHub is optional; without it no GitHub repository, mirror or `github` remote is created and the GitHub credentials are not required | | 3, 5, 7 | GitHub is optional; without it no GitHub repository, mirror or `github` remote is created and the GitHub credentials are not required |
@@ -93,5 +102,5 @@
[US-001]: ../user-stories.md [US-001]: ../user-stories.md
[SA-001]: ../stakeholder-analysis.md [SA-001]: ../stakeholder-analysis.md
[DM-001]: ./dm.md [DM-001]: ./dm.md
[02875ae]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/02875aee5f2953473924074eea0056eb31af6b7a
[2a6bb8e]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/2a6bb8e8afadfe6ca4a621da30e44a372898ca62 [2a6bb8e]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/2a6bb8e8afadfe6ca4a621da30e44a372898ca62
[ded26a6]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/ded26a658c666bf29d84093cb352e3635e07719b
+23 -6
View File
@@ -4,13 +4,13 @@
| Key | Value | | Key | Value |
| --- | --- | | --- | --- |
| ID | US-001 | | ID | US-001 |
| CrossReference | [BC-001], [UCD-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004] | | CrossReference | [BC-001], [UCD-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005] |
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-05 | Accepted | Jens Tirsvad Nielsen | S02 | Optional GitHub; choosing GitHub applies the AGPL license to the Gitea repository<br>Cited UCD-001<br>Split the epic into three stories, one per milestone | [02875ae] | | 2026-10-05 | Deprecated | Jens Tirsvad Nielsen | S02 | Added US-001.04: project details preset in config.env | [2a6bb8e] |
| 2026-10-05 | Accepted | Jens Tirsvad Nielsen | S02 | Added US-001.04: project details preset in config.env | [2a6bb8e] | | 2026-10-06 | Accepted | Jens Tirsvad Nielsen | S02 | Added US-001.05: credentials asked when missing and kept in the project .env | [ded26a6] |
--- ---
@@ -18,7 +18,7 @@
One epic: "Create a new project" ([UC-001]), setting up a new project on Gitea, optionally on GitHub, with the SQA-QC-Framework in place. The actor is the Maintainer, as in [UCD-001] (S01 or S02; for now one person holds both roles). One epic: "Create a new project" ([UC-001]), setting up a new project on Gitea, optionally on GitHub, with the SQA-QC-Framework in place. The actor is the Maintainer, as in [UCD-001] (S01 or S02; for now one person holds both roles).
The epic is split into four stories, one per milestone. Each story fits one two-week phase and can be shown working at the end of it. The epic is split into five stories, one per milestone. Each story fits one two-week phase and can be shown working at the end of it.
## Story List ## Story List
@@ -79,9 +79,25 @@ The epic is split into four stories, one per milestone. Each story fits one two-
| --- | --- | --- | | --- | --- | --- |
| [UC-001] step 3, [MIL-004] | fits one phase | Independent: needs the prompts of US-001.01 | | [UC-001] step 3, [MIL-004] | fits one phase | Independent: needs the prompts of US-001.01 |
### US-001.05 — Create a new project: ask for the credentials and keep them in the project
**As a** Maintainer, **I want** the script to ask for a credential that `.env` does not provide and to create a `.env` file in the new project, **so that** I can start without a prepared `.env` and the new project has the credentials its tools need.
**Acceptance Criteria**
- Given `GITEA_TOKEN` is not provided in `.env`, when the script starts, then it asks for it without showing what is typed and does not stop with an error; the same holds for `GITHUB_PAT` and `GITHUB_USER` when GitHub is chosen.
- Given an entered credential is not valid, when the script checks it, then it asks again and never shows the value.
- Given the project exists, when the Maintainer agrees, then the new project has a `.env` that holds only the credentials the project needs, is readable by its owner only and is ignored by git.
- Given the Maintainer declines, or `.env` already exists in the project and the Maintainer declines replacing it, then no `.env` is written or replaced and the summary says so.
- Given any run, then no credential appears in output, remotes, tracked files or the summary.
| Traces to | Size | INVEST exceptions |
| --- | --- | --- |
| [UC-001] steps 2 and 9, [MIL-005] | fits one phase | Independent: needs the local project of US-001.03 |
## INVEST Check ## INVEST Check
Valuable, Negotiable, Estimable, Small and Testable hold for each story. Independent holds only in part: the stories are ordered, each using what the one before it delivers, which follows the milestone order in [PP-001]. This is flagged as an exception on US-001.02, US-001.03 and US-001.04. Valuable, Negotiable, Estimable, Small and Testable hold for each story. Independent holds only in part: the stories are ordered, each using what the one before it delivers, which follows the milestone order in [PP-001]. This is flagged as an exception on US-001.02 to US-001.05.
--- ---
@@ -92,6 +108,7 @@ Valuable, Negotiable, Estimable, Small and Testable hold for each story. Indepen
[MIL-002]: ./milestones/mil-002-repositories-and-mirror.md [MIL-002]: ./milestones/mil-002-repositories-and-mirror.md
[MIL-003]: ./milestones/mil-003-scaffold-and-release.md [MIL-003]: ./milestones/mil-003-scaffold-and-release.md
[MIL-004]: ./milestones/mil-004-configurable-details.md [MIL-004]: ./milestones/mil-004-configurable-details.md
[MIL-005]: ./milestones/mil-005-credentials.md
[PP-001]: ./project-plan.md [PP-001]: ./project-plan.md
[02875ae]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/02875aee5f2953473924074eea0056eb31af6b7a
[2a6bb8e]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/2a6bb8e8afadfe6ca4a621da30e44a372898ca62 [2a6bb8e]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/2a6bb8e8afadfe6ca4a621da30e44a372898ca62
[ded26a6]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/ded26a658c666bf29d84093cb352e3635e07719b
+1 -1
View File
@@ -5,7 +5,7 @@
# RepoFoundry creates a Gitea repository, optionally an empty GitHub # RepoFoundry creates a Gitea repository, optionally an empty GitHub
# repository with a Gitea -> GitHub push mirror, and a local project with # repository with a Gitea -> GitHub push mirror, and a local project with
# the SQA-QC-Framework. It validates the configuration and credentials, # the SQA-QC-Framework. It validates the configuration and credentials,
# asks for the project details, checks both hosts with read-only requests # asks for the project details (those set in config.env are not asked), checks both hosts with read-only requests
# (tokens, owners, names, license, SSH) and, with --apply, creates the # (tokens, owners, names, license, SSH) and, with --apply, creates the
# repositories and the mirror, then the local project: its directory, git # repositories and the mirror, then the local project: its directory, git
# repository, remotes (no credential in any address), the framework as a # repository, remotes (no credential in any address), the framework as a
+40 -1
View File
@@ -4,7 +4,7 @@
# #
# Part of create-project.sh: sourced by it, never run on its own. # Part of create-project.sh: sourced by it, never run on its own.
# #
# Provides: unquote_value, parse_env_file, parse_env_entry, validate_config, validate_credentials, require_github_credentials, warn_if_env_unsafe, load_configuration # Provides: unquote_value, parse_env_file, parse_env_entry, validate_config, check_preset, check_preset_choice, validate_project_presets, validate_credentials, require_github_credentials, warn_if_env_unsafe, load_configuration
# unquote_value RAW: strip matching quotes (or a trailing " # comment" on an # unquote_value RAW: strip matching quotes (or a trailing " # comment" on an
# unquoted value) and return the value in REPLY. Fails on unbalanced quotes. # unquoted value) and return the value in REPLY. Fails on unbalanced quotes.
@@ -103,6 +103,45 @@ validate_config() {
CONFIG[FRAMEWORK_REPO]="${CONFIG[FRAMEWORK_REPO]:-$DEFAULT_FRAMEWORK_REPO}" CONFIG[FRAMEWORK_REPO]="${CONFIG[FRAMEWORK_REPO]:-$DEFAULT_FRAMEWORK_REPO}"
is_valid_framework_repo "${CONFIG[FRAMEWORK_REPO]}" || is_valid_framework_repo "${CONFIG[FRAMEWORK_REPO]}" ||
die "FRAMEWORK_REPO in $CONFIG_FILE must look like OWNER/NAME" die "FRAMEWORK_REPO in $CONFIG_FILE must look like OWNER/NAME"
validate_project_presets
}
# check_preset KEY VALIDATOR HINT: when KEY is set in config.env its value must
# pass VALIDATOR. A key that is present counts as set; only the description
# may be empty. The message names the key, never the value.
check_preset() {
local key="$1" validator="$2" hint="$3"
[[ -n ${CONFIG[$key]+set} ]] || return 0
if [[ -z ${CONFIG[$key]} && $key != PROJECT_DESCRIPTION ]]; then
die "$key in $CONFIG_FILE is empty; remove the line to be asked, or give a value ($hint)"
fi
"$validator" "${CONFIG[$key]}" ||
die "$key in $CONFIG_FILE is not valid: $hint"
}
# check_preset_choice KEY CHOICE...: like check_preset for a fixed list of
# words; the value is stored in lower case.
check_preset_choice() {
local key="$1"
shift
[[ -n ${CONFIG[$key]+set} ]] || return 0
[[ -n ${CONFIG[$key]} ]] ||
die "$key in $CONFIG_FILE is empty; remove the line to be asked, or give one of: $*"
CONFIG[$key]="${CONFIG[$key],,}"
in_list "${CONFIG[$key]}" "$@" ||
die "$key in $CONFIG_FILE must be one of: $*"
}
# The optional project details that may be preset in config.env.
validate_project_presets() {
check_preset PROJECT_NAME is_valid_repo_name "$HINT_REPO_NAME"
check_preset PROJECT_DESCRIPTION is_valid_description "$HINT_DESCRIPTION"
check_preset_choice PROJECT_VISIBILITY private public
check_preset GITEA_OWNER is_valid_gitea_owner "$HINT_GITEA_OWNER"
check_preset_choice USE_GITHUB yes no
check_preset GITHUB_OWNER is_valid_github_owner "$HINT_GITHUB_OWNER"
check_preset PROJECT_DIRECTORY is_valid_directory "$HINT_DIRECTORY"
check_preset_choice ENABLE_PLAN_GATE yes no
} }
validate_credentials() { validate_credentials() {
+12 -1
View File
@@ -19,11 +19,20 @@ readonly DEFAULT_SSH_PORT=10022
readonly DEFAULT_FRAMEWORK_REPO="TirSystem/SQA-QC-Framework" readonly DEFAULT_FRAMEWORK_REPO="TirSystem/SQA-QC-Framework"
readonly AGPL_LICENSE_KEY="AGPL-3.0" readonly AGPL_LICENSE_KEY="AGPL-3.0"
readonly DEFAULT_BRANCH="main" readonly DEFAULT_BRANCH="main"
# What the prompts and the preset keys in config.env both tell the Maintainer
# when a value is refused.
readonly HINT_REPO_NAME="use letters, digits, '.', '_' or '-' (at most 100), not ending in .git"
readonly HINT_DESCRIPTION="at most $MAX_DESCRIPTION_LENGTH characters and no control characters"
readonly HINT_GITEA_OWNER="use letters, digits, '.', '_' or '-' (at most 39)"
readonly HINT_GITHUB_OWNER="use letters, digits or '-' (at most 39)"
readonly HINT_DIRECTORY="must not be empty, start with '-' or contain control characters"
readonly PLAN_STEPS=("GitHub repository" "Gitea repository" "Push mirror" readonly PLAN_STEPS=("GitHub repository" "Gitea repository" "Push mirror"
"Local project" "Framework" "Skills and hooks" "Templates") "Local project" "Framework" "Skills and hooks" "Templates")
# shellcheck disable=SC2034 # read through namerefs (parse_env_file) # shellcheck disable=SC2034 # read through namerefs (parse_env_file)
readonly CONFIG_KEYS=(GITHUB_API_URL GITHUB_WEB_URL GITEA_URL GITEA_API_URL readonly CONFIG_KEYS=(GITHUB_API_URL GITHUB_WEB_URL GITEA_URL GITEA_API_URL
GITEA_SSH_PORT MIRROR_INTERVAL FRAMEWORK_REPO) GITEA_SSH_PORT MIRROR_INTERVAL FRAMEWORK_REPO
PROJECT_NAME PROJECT_DESCRIPTION PROJECT_VISIBILITY GITEA_OWNER USE_GITHUB
GITHUB_OWNER PROJECT_DIRECTORY ENABLE_PLAN_GATE)
readonly CREDENTIAL_KEYS=(GITHUB_PAT GITHUB_USER GITEA_TOKEN) readonly CREDENTIAL_KEYS=(GITHUB_PAT GITHUB_USER GITEA_TOKEN)
CONFIG_FILE="$PROJECT_ROOT/config.env" CONFIG_FILE="$PROJECT_ROOT/config.env"
@@ -41,6 +50,8 @@ TEMP_FILES=()
declare -A CONFIG=() declare -A CONFIG=()
declare -A CREDENTIALS=() declare -A CREDENTIALS=()
declare -A PROJECT=() declare -A PROJECT=()
# Project details that came from config.env instead of a prompt (PRESET[name]=1).
declare -A PRESET=()
# Facts found by the preflight checks (logins, owner kinds, repository state). # Facts found by the preflight checks (logins, owner kinds, repository state).
declare -A STATE=() declare -A STATE=()
# Outcome of each step in PLAN_STEPS, for the final report. # Outcome of each step in PLAN_STEPS, for the final report.
+56 -24
View File
@@ -4,34 +4,59 @@
# #
# Part of create-project.sh: sourced by it, never run on its own. # Part of create-project.sh: sourced by it, never run on its own.
# #
# Provides: collect_project_details, yes_no, credential_state, print_summary # Provides: preset_detail, collect_project_details, collect_github_details, source_note, yes_no, credential_state, print_summary
# preset_detail KEY NAME: a detail set in config.env is used and not asked;
# the value is returned in REPLY and marked in PRESET[NAME].
preset_detail() {
[[ -n ${CONFIG[$1]+set} ]] || return 1
REPLY="${CONFIG[$1]}"
PRESET[$2]=1
}
# Ask for each project detail, except those set in config.env.
collect_project_details() { collect_project_details() {
prompt_value "Repository name" "" is_valid_repo_name \ preset_detail PROJECT_NAME name ||
"use letters, digits, '.', '_' or '-' (at most 100), not ending in .git" prompt_value "Repository name" "" is_valid_repo_name "$HINT_REPO_NAME"
PROJECT[name]="$REPLY" PROJECT[name]="$REPLY"
prompt_value "Description (optional)" "" is_valid_description \ preset_detail PROJECT_DESCRIPTION description ||
"at most $MAX_DESCRIPTION_LENGTH characters and no control characters" prompt_value "Description (optional)" "" is_valid_description "$HINT_DESCRIPTION"
PROJECT[description]="$REPLY" PROJECT[description]="$REPLY"
prompt_choice "Visibility" private private public preset_detail PROJECT_VISIBILITY visibility ||
prompt_choice "Visibility" private private public
PROJECT[visibility]="$REPLY" PROJECT[visibility]="$REPLY"
prompt_value "Gitea owner (user or organization)" "" is_valid_gitea_owner \ preset_detail GITEA_OWNER gitea_owner ||
"use letters, digits, '.', '_' or '-' (at most 39)" prompt_value "Gitea owner (user or organization)" "" is_valid_gitea_owner "$HINT_GITEA_OWNER"
PROJECT[gitea_owner]="$REPLY" PROJECT[gitea_owner]="$REPLY"
prompt_yes_no "Also create a GitHub repository (applies the AGPL license)" y collect_github_details
preset_detail PROJECT_DIRECTORY directory ||
prompt_value "Local directory" "./${PROJECT[name]}" is_valid_directory "$HINT_DIRECTORY"
PROJECT[directory]="$REPLY"
if preset_detail ENABLE_PLAN_GATE is_plan_gate_enabled; then
[[ $REPLY == yes ]] && REPLY=1 || REPLY=0
else
prompt_yes_no "Enable the plan gate" n
fi
PROJECT[is_plan_gate_enabled]="$REPLY"
}
# Whether GitHub is used, and its owner. A GITHUB_OWNER set while GitHub is
# not used is ignored, with a warning.
collect_github_details() {
if preset_detail USE_GITHUB has_github; then
[[ $REPLY == yes ]] && REPLY=1 || REPLY=0
else
prompt_yes_no "Also create a GitHub repository (applies the AGPL license)" y
fi
PROJECT[has_github]="$REPLY" PROJECT[has_github]="$REPLY"
PROJECT[github_owner]="" PROJECT[github_owner]=""
if ((PROJECT[has_github])); then if ((PROJECT[has_github])); then
prompt_value "GitHub owner (user or organization)" \ preset_detail GITHUB_OWNER github_owner ||
"${CREDENTIALS[GITHUB_USER]:-}" is_valid_github_owner \ prompt_value "GitHub owner (user or organization)" "${CREDENTIALS[GITHUB_USER]:-}" is_valid_github_owner "$HINT_GITHUB_OWNER"
"use letters, digits or '-' (at most 39)"
PROJECT[github_owner]="$REPLY" PROJECT[github_owner]="$REPLY"
elif [[ -n ${CONFIG[GITHUB_OWNER]+set} ]]; then
warn "GITHUB_OWNER in $CONFIG_FILE is ignored because GitHub is not used"
fi fi
prompt_value "Local directory" "./${PROJECT[name]}" is_valid_directory \
"must not be empty, start with '-' or contain control characters"
PROJECT[directory]="$REPLY"
prompt_yes_no "Enable the plan gate" n
PROJECT[is_plan_gate_enabled]="$REPLY"
} }
yes_no() { yes_no() {
@@ -50,20 +75,27 @@ credential_state() {
fi fi
} }
# source_note NAME: the marker shown after a value that came from config.env.
source_note() {
if [[ -n ${PRESET[$1]:-} ]]; then
printf ' (from config.env)'
fi
}
print_summary() { print_summary() {
say "" say ""
say "$PROJECT_NAME $VERSION" say "$PROJECT_NAME $VERSION"
say "Collected details:" say "Collected details:"
say " Repository : ${PROJECT[name]} (${PROJECT[visibility]})" say " Repository : ${PROJECT[name]}$(source_note name) (${PROJECT[visibility]}$(source_note visibility))"
say " Description : ${PROJECT[description]:-(none)}" say " Description : ${PROJECT[description]:-(none)}$(source_note description)"
say " Gitea : ${CONFIG[GITEA_URL]}/${PROJECT[gitea_owner]}/${PROJECT[name]}" say " Gitea : ${CONFIG[GITEA_URL]}/${PROJECT[gitea_owner]}/${PROJECT[name]}$(source_note gitea_owner)"
if ((PROJECT[has_github])); then if ((PROJECT[has_github])); then
say " GitHub : ${CONFIG[GITHUB_WEB_URL]}/${PROJECT[github_owner]}/${PROJECT[name]} (AGPL license applied)" say " GitHub : ${CONFIG[GITHUB_WEB_URL]}/${PROJECT[github_owner]}/${PROJECT[name]} (AGPL license applied)$(source_note github_owner)"
else else
say " GitHub : not used" say " GitHub : not used$(source_note has_github)"
fi fi
say " Directory : ${PROJECT[directory]}" say " Directory : ${PROJECT[directory]}$(source_note directory)"
say " Plan gate : $(yes_no "${PROJECT[is_plan_gate_enabled]}")" say " Plan gate : $(yes_no "${PROJECT[is_plan_gate_enabled]}")$(source_note is_plan_gate_enabled)"
say "Credentials : GITEA_TOKEN $(credential_state GITEA_TOKEN)," \ say "Credentials : GITEA_TOKEN $(credential_state GITEA_TOKEN)," \
"GITHUB_PAT $(credential_state GITHUB_PAT)" "GITHUB_PAT $(credential_state GITHUB_PAT)"
} }
+275
View File
@@ -0,0 +1,275 @@
#!/usr/bin/env bash
# test-presets.sh - tests for the project details preset in config.env
# (MIL-004): a detail that is set there is not asked, an invalid one stops
# the run, and the confirmations stay interactive. Sourced by run-tests.sh.
# shellcheck disable=SC2016 # snippet and fixture text is literal on purpose
# The eight keys with a value that differs from the answer given when asked.
readonly PRESET_ALL='PROJECT_NAME=preset-app
PROJECT_DESCRIPTION=From the config
PROJECT_VISIBILITY=public
GITEA_OWNER=PresetOrg
USE_GITHUB=yes
GITHUB_OWNER=preset-gh
PROJECT_DIRECTORY=./preset-dir
ENABLE_PLAN_GATE=yes'
# The same details as ANSWERS_GITHUB, so a run with all of them preset is the
# same run with no answers.
readonly PRESET_LIKE_ANSWERS='PROJECT_NAME=my-app
PROJECT_DESCRIPTION=A test app
PROJECT_VISIBILITY=private
GITEA_OWNER=TirSystem
USE_GITHUB=yes
GITHUB_OWNER=acme-org
PROJECT_DIRECTORY=./my-app
ENABLE_PLAN_GATE=no'
# collect_with PRESET_LINES INPUT: parse a config holding the preset lines,
# collect the details and print them one per line.
collect_with() {
printf '%s\n' "$1" >"$WORK/preset.env"
run_lib "$2" 'parse_env_file "'"$WORK"'/preset.env" CONFIG_KEYS CONFIG
validate_project_presets
collect_project_details
for k in name description visibility gitea_owner has_github github_owner directory is_plan_gate_enabled; do
printf "%s=%s\n" "$k" "${PROJECT[$k]}"
done'
}
# Each key, the answers a run gives for the other seven details, and the
# prompt that must not be shown for the key.
test_each_key_is_used_and_not_asked() {
local key value field label line input
local -A answer=([PROJECT_NAME]=asked-app [PROJECT_DESCRIPTION]="Asked description"
[PROJECT_VISIBILITY]=private [GITEA_OWNER]=AskedOrg [USE_GITHUB]=y
[GITHUB_OWNER]=asked-gh [PROJECT_DIRECTORY]=./asked-dir [ENABLE_PLAN_GATE]=n)
local order=(PROJECT_NAME PROJECT_DESCRIPTION PROJECT_VISIBILITY GITEA_OWNER
USE_GITHUB GITHUB_OWNER PROJECT_DIRECTORY ENABLE_PLAN_GATE)
while IFS='|' read -r key value field label; do
input=""
for line in "${order[@]}"; do
if [[ $line != "$key" ]]; then
input+="${answer[$line]}"$'\n'
fi
done
collect_with "$key=$value" "$input"
assert_status "$key preset" 0 "$STATUS"
assert_contains "$key value used" "$OUT" "$field"
assert_not_contains "$key not asked" "$ERR" "$label"
assert_contains "other details still asked" "$OUT" "asked"
done <<'EOF'
PROJECT_NAME|preset-app|name=preset-app|Repository name
PROJECT_DESCRIPTION|From the config|description=From the config|Description
PROJECT_VISIBILITY|public|visibility=public|Visibility
GITEA_OWNER|PresetOrg|gitea_owner=PresetOrg|Gitea owner
USE_GITHUB|yes|has_github=1|Also create a GitHub
GITHUB_OWNER|preset-gh|github_owner=preset-gh|GitHub owner
PROJECT_DIRECTORY|./preset-dir|directory=./preset-dir|Local directory
ENABLE_PLAN_GATE|yes|is_plan_gate_enabled=1|Enable the plan gate
EOF
}
test_all_keys_set_asks_nothing() {
collect_with "$PRESET_ALL" ""
assert_status "no input needed" 0 "$STATUS"
assert_eq "every value from the config" $'name=preset-app\ndescription=From the config\nvisibility=public\ngitea_owner=PresetOrg\nhas_github=1\ngithub_owner=preset-gh\ndirectory=./preset-dir\nis_plan_gate_enabled=1' "$OUT"
assert_eq "no prompt text at all" "" "$ERR"
}
test_absent_keys_are_asked_as_before() {
collect_with "PROJECT_NAME=preset-app" $'\n\nTirSystem\nn\n\nn\n'
assert_status "mixed" 0 "$STATUS"
assert_contains "preset name" "$OUT" "name=preset-app"
assert_contains "default directory from the preset name" "$OUT" "directory=./preset-app"
assert_contains "other details asked" "$ERR" "Gitea owner"
}
test_values_are_taken_in_any_case() {
collect_with $'PROJECT_VISIBILITY=PUBLIC\nUSE_GITHUB=No\nENABLE_PLAN_GATE=YES' $'x-app\n\nTirSystem\n\n'
assert_status "case ignored" 0 "$STATUS"
assert_contains "visibility" "$OUT" "visibility=public"
assert_contains "no GitHub" "$OUT" "has_github=0"
assert_contains "plan gate" "$OUT" "is_plan_gate_enabled=1"
}
# ------------------------------------------------------- empty and invalid
test_empty_value_counts_as_set_only_for_the_description() {
collect_with "PROJECT_DESCRIPTION=" $'my-app\npublic\nTirSystem\nn\n\nn\n'
assert_status "empty description accepted" 0 "$STATUS"
assert_contains "description empty" "$OUT" "description="
assert_not_contains "description not asked" "$ERR" "Description"
local key
for key in PROJECT_NAME PROJECT_VISIBILITY GITEA_OWNER USE_GITHUB GITHUB_OWNER \
PROJECT_DIRECTORY ENABLE_PLAN_GATE; do
printf '%s=\n' "$key" >"$WORK/preset.env"
run_lib "" 'parse_env_file "'"$WORK"'/preset.env" CONFIG_KEYS CONFIG
validate_project_presets'
assert_status "$key empty" 1 "$STATUS"
assert_contains "$key named" "$ERR" "$key in"
assert_contains "$key says empty" "$ERR" "is empty"
done
}
test_invalid_values_are_refused_naming_the_key() {
local key value
while IFS='|' read -r key value; do
printf '%s=%s\n' "$key" "$value" >"$WORK/preset.env"
run_lib "" 'parse_env_file "'"$WORK"'/preset.env" CONFIG_KEYS CONFIG
validate_project_presets'
assert_status "$key=$value" 1 "$STATUS"
assert_contains "$key=$value named" "$ERR" "$key in"
done <<'EOF'
PROJECT_NAME|bad name
PROJECT_NAME|x.git
PROJECT_VISIBILITY|internal
GITEA_OWNER|-lead
USE_GITHUB|maybe
USE_GITHUB|1
GITHUB_OWNER|octo_user
PROJECT_DIRECTORY|-rf
ENABLE_PLAN_GATE|true
EOF
# A description over the limit (350 characters) is refused too.
printf 'PROJECT_DESCRIPTION=%s\n' "$(printf 'a%.0s' $(seq 1 351))" >"$WORK/preset.env"
run_lib "" 'parse_env_file "'"$WORK"'/preset.env" CONFIG_KEYS CONFIG
validate_project_presets'
assert_status "long description" 1 "$STATUS"
assert_contains "named" "$ERR" "PROJECT_DESCRIPTION in"
}
test_invalid_value_stops_before_any_request_and_never_asks() {
setup_hosts
printf 'PROJECT_NAME=bad name\n' >>"$WORK/config.env"
run_apply ""
assert_status "stopped" 1 "$STATUS"
assert_contains "key named" "$ERR" "PROJECT_NAME in"
assert_not_contains "no value asked instead" "$ERR" "Repository name:"
assert_eq "no request made" "" "$(calls)"
}
test_new_keys_are_rejected_in_env_and_credentials_in_config() {
setup_hosts
printf 'PROJECT_NAME=my-app\n' >>"$WORK/.env"
run_dry ""
assert_status ".env with a project key" 1 "$STATUS"
assert_contains "unknown in .env" "$ERR" "unknown key 'PROJECT_NAME'"
setup_hosts
printf 'GITEA_TOKEN=abcdefgh12345\n' >>"$WORK/config.env"
run_dry ""
assert_status "config.env with a credential" 1 "$STATUS"
assert_contains "credential refused" "$ERR" "is a credential"
}
# ------------------------------------------------------------- GitHub
test_use_github_no_skips_the_owner_and_warns_about_a_stray_one() {
collect_with $'USE_GITHUB=no\nGITHUB_OWNER=acme-org' $'my-app\n\n\nTirSystem\n\nn\n'
assert_status "GitHub off" 0 "$STATUS"
assert_contains "no GitHub" "$OUT" "has_github=0"
assert_contains "no owner" "$OUT" "github_owner="
assert_not_contains "owner not asked" "$ERR" "GitHub owner ("
assert_contains "ignored with a warning" "$ERR" "GITHUB_OWNER in"
assert_contains "says why" "$ERR" "ignored because GitHub is not used"
collect_with "USE_GITHUB=no" $'my-app\n\n\nTirSystem\n\nn\n'
assert_not_contains "no warning without the key" "$ERR" "ignored"
}
test_github_owner_preset_is_used_when_github_is_asked_for() {
collect_with "GITHUB_OWNER=preset-gh" $'my-app\n\n\nTirSystem\ny\n\nn\n'
assert_contains "owner from the config" "$OUT" "github_owner=preset-gh"
assert_not_contains "owner not asked" "$ERR" "GitHub owner ("
collect_with "GITHUB_OWNER=preset-gh" $'my-app\n\n\nTirSystem\nn\n\nn\n'
assert_contains "ignored when answered no" "$ERR" "ignored because GitHub is not used"
}
test_use_github_no_makes_no_github_call() {
setup_hosts
printf '%s\n' "$PRESET_LIKE_ANSWERS" | sed 's/^USE_GITHUB=.*/USE_GITHUB=no/' >>"$WORK/config.env"
run_dry ""
assert_status "dry run" 0 "$STATUS"
assert_contains "GitHub not used" "$OUT" "GitHub repository : not used"
assert_not_contains "no GitHub call" "$(calls)" "api.github.com"
assert_not_contains "no GitHub owner asked" "$ERR" "GitHub owner ("
}
# ------------------------------------------------------------ the summary
test_summary_marks_the_values_from_config_env() {
setup_hosts
printf '%s\n' "$PRESET_LIKE_ANSWERS" >>"$WORK/config.env"
run_dry ""
assert_status "all preset" 0 "$STATUS"
assert_contains "name" "$OUT" "Repository : my-app (from config.env) (private (from config.env))"
assert_contains "description" "$OUT" "Description : A test app (from config.env)"
assert_contains "Gitea" "$OUT" "/TirSystem/my-app (from config.env)"
assert_contains "GitHub" "$OUT" "(AGPL license applied) (from config.env)"
assert_contains "directory" "$OUT" "Directory : ./my-app (from config.env)"
assert_contains "plan gate" "$OUT" "Plan gate : no (from config.env)"
}
test_summary_marks_nothing_when_everything_is_asked() {
setup_hosts
run_dry "$ANSWERS_GITHUB"
assert_status "all asked" 0 "$STATUS"
assert_not_contains "no marker" "$OUT" "(from config.env)"
}
# ----------------------------------------------------- the confirmations
test_with_every_detail_set_only_the_confirmations_are_asked() {
setup_hosts
run_apply "$ANSWERS_GITHUB"y$'\n'
local asked_calls
asked_calls="$(calls)"
remove_workdir
new_workdir
setup_hosts
printf '%s\n' "$PRESET_LIKE_ANSWERS" >>"$WORK/config.env"
run_apply $'y\n'
assert_status "run with presets" 0 "$STATUS"
assert_eq "same requests as the run that was asked" "$asked_calls" "$(calls)"
assert_contains "created" "$OUT" "This is what exists now"
}
test_with_every_detail_set_create_now_is_still_asked_and_defaults_to_no() {
setup_hosts
printf '%s\n' "$PRESET_LIKE_ANSWERS" >>"$WORK/config.env"
run_apply $'n\n'
assert_status "declined" 0 "$STATUS"
assert_contains "says so" "$OUT" "Nothing was created."
assert_not_contains "nothing created" "$(calls)" "POST"
setup_hosts
printf '%s\n' "$PRESET_LIKE_ANSWERS" >>"$WORK/config.env"
run_apply $'\n'
assert_contains "empty answer means no" "$OUT" "Nothing was created."
assert_not_contains "no POST on the default" "$(calls)" "POST"
setup_hosts
printf '%s\n' "$PRESET_LIKE_ANSWERS" >>"$WORK/config.env"
run_apply ""
assert_contains "no answer means no" "$OUT" "Nothing was created."
assert_not_contains "nothing created without an answer" "$(calls)" "POST"
}
test_with_every_detail_set_an_existing_directory_is_not_replaced() {
setup_hosts
printf '%s\n' "$PRESET_LIKE_ANSWERS" >>"$WORK/config.env"
mkdir -p "$WORK/my-app"
printf 'keep\n' >"$WORK/my-app/mine.txt"
run_apply $'y\n\n'
assert_file_exists "existing file kept" "$WORK/my-app/mine.txt"
assert_eq "content kept" "keep" "$(cat "$WORK/my-app/mine.txt")"
}
test_a_quoted_description_may_contain_a_hash() {
local answers=$'my-app\npublic\nTirSystem\nn\n\nn\n'
collect_with 'PROJECT_DESCRIPTION="Tool for #mirrors"' "$answers"
assert_status "quoted" 0 "$STATUS"
assert_contains "whole value kept" "$OUT" "description=Tool for #mirrors"
# Unquoted, the same text is cut at the comment mark, as documented.
collect_with 'PROJECT_DESCRIPTION=Tool for #mirrors' "$answers"
assert_contains "cut at the comment" "$OUT" "description=Tool for"
assert_not_contains "comment dropped" "$OUT" "mirrors"
}