Author SHA1 Message Date
Tirsvad 56d44989ca Bump the version to 0.3.2
Set VERSION in src/lib/constants.sh to 0.3.2 and the --version check in
tests/test-security.sh to match. This is task 5 of MIL-009, the Git excludes
step. Release v0.3.2 is tagged on Gitea from the merge commit once the pull
request is merged.

Refs #69
Task: MIL-009#5
2026-10-08 14:33:05 +08:00
Tirsvad 642ea775d1 Merge pull request 'Test the Git excludes step (task 4)' (#73) from mil-009-tests into main
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 4s
Reviewed-on: #73
2026-10-08 08:30:53 +02:00
Tirsvad 7c58c47d8e Test the Git excludes step
Add tests for the step that excludes .claude, .agents and AGENTS.md from git
in the new project: the three paths are ignored and absent from git status
after a run, while framework, .gitmodules and docs/artifact-registry.md stay
visible; no .gitignore is written and nothing is committed; a second run
writes each entry and the comment once and keeps the existing lines, also
when the file has no final newline; nothing is written for a path git
already ignores; a tracked path stays tracked and is named; the step is
skipped without the framework; the dry-run plan lists it.

The existing .env exclusion test also checks its comment line.

Refs #68
Task: MIL-009#4
2026-10-08 14:28:55 +08:00
Tirsvad e23ef3a4b6 Merge pull request 'Describe the files git ignores in the README (task 3)' (#72) from mil-009-readme into main
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 5s
Reviewed-on: #72
2026-10-08 08:21:54 +02:00
Tirsvad cc157816c7 Describe the files git ignores in the README
Add the "Git excludes" step to the overview, the sample plan, the numbered
run steps and a new section: which paths are excluded and why, that nothing
is committed or changed in a tracked file, that the entries live in
.git/info/exclude and so are not shared with a clone, what happens to a path
git already tracks, and how to track one anyway. Add the tracked-path case to
the error table and the new roles of git.sh and framework.sh to the code
layout.

Closes #67
2026-10-08 14:20:03 +08:00
Tirsvad e9872fa3ed Merge pull request 'Accept MIL-009 and add the Git excludes step (task 2)' (#71) from mil-009-exclude-framework-files into main
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 4s
Reviewed-on: #71
2026-10-08 08:13:52 +02:00
Tirsvad c379dd4d75 Resolve pending commit links for RC-032 2026-10-08 14:06:46 +08:00
Tirsvad 4058ab4e3e Exclude .claude, .agents and AGENTS.md from git in the new project
After the templates are copied, create-project.sh adds /.claude, /.agents and
/AGENTS.md to .git/info/exclude of the new project as its own step, "Git
excludes". No .gitignore or tracked file changes and nothing is committed.
The step is skipped when the framework steps are skipped, and a path git
already tracks stays tracked and is named in the step.

The .git/info/exclude code of exclude_env_file moves into the shared helper
exclude_from_git (git.sh), which both now use; the .env behavior is unchanged.

Refs #66
Task: MIL-009#2
2026-10-08 14:06:37 +08:00
Tirsvad 1b1b6bec2e Accept MIL-009 after review RC-032
S02 gave the Go in chat on 2026-10-08 and waived the PlantUML render check
(no PlantUML server is configured). Add the review record RC-032, set the
latest Version History rows of MIL-009 and the changed documents to Accepted
and the rows before them to Deprecated, and record the review in the
traceability matrix.
2026-10-08 14:06:36 +08:00
Tirsvad 4287d218ae Merge pull request 'Plan MIL-009: .claude, .agents and AGENTS.md are excluded from git' (#70) from mil-009-exclude-framework-files into main
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 4s
Reviewed-on: #70
2026-10-08 07:47:12 +02:00
Tirsvad 4f6204c58e Resolve pending commit links for MIL-009 2026-10-08 13:45:56 +08:00
Tirsvad 08cb484498 Plan MIL-009: .claude, .agents and AGENTS.md are excluded from git
Add the phase MIL-009 (proposed 2026-12-21 to 2026-12-23): after the
framework is installed, the new project excludes .claude, .agents and
AGENTS.md through its own .git/info/exclude, so no tracked file changes.

The analysis and design documents agree with it: Business Case objective 5,
US-001.03, UC-001 (postcondition, step 9, extension 9f, a rule), OC-001 (P15
and two exceptions), SD-001, DCD-001 and DCD-002 (excludeFromGit,
trackedPaths), and the Framework Setup and Template definitions in DM-001,
DM-002 and the dictionary.

Refs #65
Refs #66
Refs #67
Refs #68
Refs #69
2026-10-08 13:45:41 +08:00
Tirsvad b1c1fbf178 Merge pull request 'Link the token how-to from the README credentials section' (#64) from howto-link-credentials into main
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 3s
Reviewed-on: #64
2026-10-08 07:06:45 +02:00
Tirsvad 993722b742 Link the token how-to from the README credentials section
The `.env` (credentials) section now points to howto/create-access-tokens.md
next to the Token permissions reference, so a reader who needs a token finds
the step-by-step guide where the credentials are described.
2026-10-08 13:06:01 +08:00
Tirsvad b0925a51aa Merge pull request 'Add a step-by-step how-to for the Gitea and GitHub tokens' (#63) from howto-access-tokens into main
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 3s
Reviewed-on: #63
2026-10-08 06:58:07 +02:00
Tirsvad 08d0371da5 Add a step-by-step how-to for the Gitea and GitHub tokens
howto/create-access-tokens.md walks through creating the Gitea access token
and the GitHub classic personal access token that RepoFoundry needs, with nine
illustrations in howto/img/, how to hand the tokens to the script, how to keep
them safe and what the script's token errors mean.

The README's Token permissions section now links to it.
2026-10-08 12:54:46 +08:00
Tirsvad b90050e6c4 Merge pull request 'Gitea is the only remote (MIL-008) and version 0.3.1' (#58) from mil-008-gitea-only-remote into main
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 7s
Reviewed-on: #58
2026-10-08 05:44:59 +02:00
Tirsvad 538e44c3a5 Bump the version to 0.3.1
Task: MIL-008#4
2026-10-08 11:27:50 +08:00
Tirsvad 19de9cd9a5 Record S02's acceptance of MIL-008 and RC-031
- RC-031 is Accepted with a Go verdict and no open action items
- MIL-008 and the documents it changes are set to Accepted (previous rows
  Deprecated): BC-001, US-001, UC-001, OC-001, SD-001, DCD-001, DCD-002,
  DM-001, DM-002, MIL-003, PP-001 and TM-001
2026-10-08 11:27:49 +08:00
Tirsvad 310d776605 Resolve pending commit links for the MIL-008 task 4 2026-10-08 11:26:54 +08:00
Tirsvad 1056639d5b Add the version bump to MIL-008 as task 4
The version is raised to 0.3.1 and release v0.3.1 is tagged on Gitea from the
merge commit once the pull request is merged. MIL-008 gets task 4 and
criterion 7; RC-031 counts seven criteria.
2026-10-08 11:26:53 +08:00
Tirsvad 43c2bbb3de Resolve pending commit links for the MIL-008 plan 2026-10-08 11:13:31 +08:00
Tirsvad 79f6edeb38 Add only the origin remote to the local project
create_local_project no longer adds a github remote, with or without GitHub:
a push to origin reaches GitHub through the push mirror. A github remote that
already exists, such as one made by an earlier version, is left as it is, and
a different origin is still refused.

- Remove github_remote_url, which nothing else used
- README describes the one remote and how to remove an old github remote
- Tests: origin is the only remote with and without GitHub, no GitHub address
  in .git/config, and an existing github remote is kept

Task: MIL-008#1
Task: MIL-008#2
Task: MIL-008#3
2026-10-08 11:13:21 +08:00
Tirsvad 039a28c01b Plan MIL-008: the local project has origin as its only remote
A project created by the script had two remotes, origin (Gitea) and github.
Gitea already pushes to GitHub through the push mirror, so the second remote
is not needed and invites a push that goes around the mirror.

- MIL-008 and its review record RC-031
- Objective 4, US-001.03, UC-001 step 8, OC-001 P9, SD-001, DCD-001, DCD-002,
  DM-001 and DM-002 describe one remote; MIL-003 criterion 1 and task 1 follow
- Project plan and traceability matrix list MIL-008 and RC-031
2026-10-08 11:13:09 +08:00
Tirsvad f64c4bd2f0 Merge pull request 'Docs: record S02's acceptance of RC-022 to RC-030' (#57) from docs-accept-reviews into main
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Failing after 6s
Reviewed-on: #57
2026-10-07 10:38:55 +02:00
40 changed files with 1011 additions and 160 deletions
+60 -11
View File
@@ -5,10 +5,11 @@ RepoFoundry (`src/create-project.sh`) sets up a new project in one run:
- a **Gitea** repository (the source of truth), - a **Gitea** repository (the source of truth),
- optionally an empty **GitHub** repository that receives everything through a - optionally an empty **GitHub** repository that receives everything through a
**push mirror from Gitea to GitHub**, **push mirror from Gitea to GitHub**,
- and a **local project** with credential-free remotes and the - and a **local project** with one credential-free remote, `origin` (Gitea), and the
[SQA-QC-Framework](https://git.tirsystem.com/TirSystem/SQA-QC-Framework) [SQA-QC-Framework](https://git.tirsystem.com/TirSystem/SQA-QC-Framework)
added as a git submodule, with its skills, git hooks (and optionally the plan added as a git submodule, with its skills, git hooks (and optionally the plan
gate) and templates installed. gate) and templates installed. The files it installs (`.claude`, `.agents` and
`AGENTS.md`) are excluded from git in the new project.
It is a Bash script. It asks for the repository name, description, visibility It is a Bash script. It asks for the repository name, description, visibility
and owner (a user or an organization, separately on each host), shows a plan, and owner (a user or an organization, separately on each host), shows a plan,
@@ -201,7 +202,8 @@ src/create-project.sh --apply # asks only "Create these now (y/n) [n]"
`.env` is ignored by git. The script warns if it is readable by other users or `.env` is ignored by git. The script warns if it is readable by other users or
not ignored by git. See [Token permissions](#token-permissions) for what each not ignored by git. See [Token permissions](#token-permissions) for what each
token needs. token needs, and [How to create the access tokens](howto/create-access-tokens.md)
for the steps, with pictures, to create the Gitea and the GitHub token.
`.env` is optional, and so is each key in it. A credential that is not `.env` is optional, and so is each key in it. A credential that is not
provided (the file is missing, the key is absent or its value is empty) is provided (the file is missing, the key is absent or its value is empty) is
@@ -258,6 +260,7 @@ Plan:
Framework : add ssh://git@git.example.org:10022/Team/SQA-QC-Framework.git as a submodule Framework : add ssh://git@git.example.org:10022/Team/SQA-QC-Framework.git as a submodule
Skills and hooks : install once; plan gate no Skills and hooks : install once; plan gate no
Templates : AGENTS.md and docs/artifact-registry.md (you are asked before a file is replaced) Templates : AGENTS.md and docs/artifact-registry.md (you are asked before a file is replaced)
Git excludes : /.claude /.agents /AGENTS.md go into .git/info/exclude (no tracked file changes)
``` ```
Without `--apply` that is all that happens. With `--apply` the script asks Without `--apply` that is all that happens. With `--apply` the script asks
@@ -266,12 +269,15 @@ Without `--apply` that is all that happens. With `--apply` the script asks
1. the GitHub repository (empty), if chosen; 1. the GitHub repository (empty), if chosen;
2. the Gitea repository (with the license that applies: `PROJECT_LICENSE`, or AGPL-3.0 for a public project with GitHub); 2. the Gitea repository (with the license that applies: `PROJECT_LICENSE`, or AGPL-3.0 for a public project with GitHub);
3. the push mirror Gitea -> GitHub, and a request for its first sync; 3. the push mirror Gitea -> GitHub, and a request for its first sync;
4. the local directory, `git init` on `main`, the `origin` remote (and `github` 4. the local directory, `git init` on `main`, the `origin` remote (the only
if chosen), and, if the Gitea repository holds the license commit, that remote: a push to it reaches GitHub through the mirror) and, if the Gitea
history; repository holds the license commit, that history;
5. the framework as the submodule `framework`; 5. the framework as the submodule `framework`;
6. the framework's skills and git hooks, and the plan gate if chosen; 6. the framework's skills and git hooks, and the plan gate if chosen;
7. `AGENTS.md` and `docs/artifact-registry.md` from the framework's templates. 7. `AGENTS.md` and `docs/artifact-registry.md` from the framework's templates;
8. `/.claude`, `/.agents` and `/AGENTS.md` added to the new project's
`.git/info/exclude`, so git does not list them (see
[The files git ignores](#the-files-git-ignores-in-the-new-project)).
No commit is made in the new project. Work on a branch there: the framework's No commit is made in the new project. Work on a branch there: the framework's
hooks refuse commits on `main`. hooks refuse commits on `main`.
@@ -297,6 +303,39 @@ it replaces an existing `core.hooksPath`, and before it replaces an existing
replaces a remote that points somewhere else, and git itself refuses to replaces a remote that points somewhere else, and git itself refuses to
overwrite a file when the license history is checked out. overwrite a file when the license history is checked out.
### The files git ignores in the new project
The framework installs files that are copies, not the project's own work. After
the templates are copied, the script makes git ignore three paths in the new
project by adding them to its `.git/info/exclude`:
| Path | What it is |
| --- | --- |
| `/.claude` | the skills for the standalone Claude Code CLI (the whole folder) |
| `/.agents` | the skills for Codex CLI and other tools that read `.agents/skills` (the whole folder) |
| `/AGENTS.md` | the project instructions, copied from the framework's template |
- **Nothing is committed and no tracked file changes.** `.gitignore` is not
touched, and `framework`, `.gitmodules` and `docs/artifact-registry.md` stay
visible to git: they are part of the project.
- **The entries belong to this clone.** `.git/info/exclude` is never committed
or pushed, so a fresh clone has neither the entries nor these files. There,
run `git submodule update --init --recursive` and then
`bash framework/scripts/install-skills.sh` to make the skills again.
`AGENTS.md` cannot be made again that way: it is a copy of a template that
you edit, so what you add to it exists only in the clone where you wrote it.
- **The whole `.claude` and `.agents` folders are ignored,** not only their
`skills` folders, so anything else you keep there (settings, agents) is
ignored too.
- **A path git already tracks stays tracked,** because an exclusion does not
apply to a tracked file. The script never removes anything from git; the
summary names the path ("git tracks AGENTS.md, so it is not ignored"). Run
`git rm --cached` on it yourself if you want it ignored.
- **Without SSH to Gitea** the framework steps are skipped, and so is this one.
- **To track one of the paths anyway,** delete its line from
`.git/info/exclude` in the project and `git add` it. Running the script again
in that directory adds the line back.
## SSH access to Gitea ## SSH access to Gitea
The framework submodule is fetched over SSH on port **10022** The framework submodule is fetched over SSH on port **10022**
@@ -325,6 +364,9 @@ Both tokens go in `.env` (never in `config.env`, never in a remote URL). The
script sends them only in a request header, through a private temporary file, script sends them only in a request header, through a private temporary file,
and never prints them. and never prints them.
New to this? [How to create the access tokens](howto/create-access-tokens.md)
walks through both, step by step, with pictures.
### GitHub token (`GITHUB_PAT`, only when you choose GitHub) ### GitHub token (`GITHUB_PAT`, only when you choose GitHub)
The same token has two jobs: it creates the repository, and it is the The same token has two jobs: it creates the repository, and it is the
@@ -379,8 +421,10 @@ script stops before it creates anything when a preflight check is refused.
- **No destructive commands.** The script never deletes a repository or a - **No destructive commands.** The script never deletes a repository or a
file and never uses a recursive delete; temporary files are removed one by file and never uses a recursive delete; temporary files are removed one by
one. one.
- **Credential-free remotes.** `origin` is `ssh://git@host:port/owner/name.git` - **Credential-free remote.** `origin` is `ssh://git@host:port/owner/name.git`
(or plain HTTPS when SSH is not used) and `github` is a plain HTTPS address. (or plain HTTPS when SSH is not used). There is no `github` remote: push to
`origin` and the mirror carries it to GitHub. A `github` remote made by an
earlier version is left alone; remove it with `git remote remove github`.
- **Redirects are not followed,** so a token is only ever sent to the host in - **Redirects are not followed,** so a token is only ever sent to the host in
the URL it was meant for. Unknown SSH host keys are refused. the URL it was meant for. Unknown SSH host keys are refused.
- **Framework scripts run on the new project only.** They are run with - **Framework scripts run on the new project only.** They are run with
@@ -404,6 +448,7 @@ step, `2` a usage error.
| A step fails after another succeeded | stops and prints what exists, what failed and how to continue | fix the cause and run the **same command again with `--apply`**: what was created is offered for reuse | | A step fails after another succeeded | stops and prints what exists, what failed and how to continue | fix the cause and run the **same command again with `--apply`**: what was created is offered for reuse |
| The mirror is refused (disabled, interval too short) | keeps the repositories and reports it | change `MIRROR_INTERVAL` or ask the Gitea administrator, then run again | | The mirror is refused (disabled, interval too short) | keeps the repositories and reports it | change `MIRROR_INTERVAL` or ask the Gitea administrator, then run again |
| The framework submodule cannot be fetched | reports the address and how to test SSH | fix your SSH access, run again | | The framework submodule cannot be fetched | reports the address and how to test SSH | fix your SSH access, run again |
| Git already tracks `.claude`, `.agents` or `AGENTS.md` in the project | leaves it tracked and names it in the summary; the other paths are excluded | `git rm --cached` it if you want it ignored |
| `sync_on_commit` was ignored by Gitea | warns; the mirror syncs on its interval | enable it in the repository settings if needed | | `sync_on_commit` was ignored by Gitea | warns; the mirror syncs on its interval | enable it in the repository settings if needed |
A partial run is reported like this: A partial run is reported like this:
@@ -447,6 +492,10 @@ web interface and the project directory by hand.
copy. copy.
- **The framework needs SSH.** Without SSH access to Gitea the framework steps - **The framework needs SSH.** Without SSH access to Gitea the framework steps
can only be skipped. can only be skipped.
- **The ignored framework files are not shared.** `.claude`, `.agents` and
`AGENTS.md` are excluded in the clone the script made, not in the repository,
so another clone does not have them (see
[The files git ignores](#the-files-git-ignores-in-the-new-project)).
- **Tested on Windows (Git Bash) only so far.** Running the tests on Linux and - **Tested on Windows (Git Bash) only so far.** Running the tests on Linux and
macOS is an open follow-up. macOS is an open follow-up.
@@ -478,9 +527,9 @@ file. The files are loaded from that directory only, by a fixed path.
| `plan.sh` | printing what the script is about to do | | `plan.sh` | printing what the script is about to do |
| `repositories.sh` | creating the GitHub and Gitea repositories | | `repositories.sh` | creating the GitHub and Gitea repositories |
| `mirror.sh` | the Gitea to GitHub push mirror | | `mirror.sh` | the Gitea to GitHub push mirror |
| `git.sh` | running git for the new project without prompts or tokens on a command line | | `git.sh` | running git for the new project without prompts or tokens on a command line, and adding entries to its `.git/info/exclude` |
| `localproject.sh` | the local directory, git repository and remotes | | `localproject.sh` | the local directory, git repository and remotes |
| `framework.sh` | the framework submodule, skills, hooks and templates | | `framework.sh` | the framework submodule, skills, hooks and templates, and the git excludes for the files it installs |
| `apply.sh` | confirmations and the apply flow; the only code that changes anything | | `apply.sh` | confirmations and the apply flow; the only code that changes anything |
| `cli.sh` | usage text and option parsing | | `cli.sh` | usage text and option parsing |
+2 -2
View File
@@ -14,7 +14,7 @@ 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 | 008 | | MIL | Milestone / Gateway | docs/milestones/*.md | 010 |
| US | User Story | docs/user-stories.md | 002 | | US | User Story | docs/user-stories.md | 002 |
| UC | Use Case | docs/uc-*/uc.md | 003 | | UC | Use Case | docs/uc-*/uc.md | 003 |
| SSD | System Sequence Diagram | docs/uc-*/ssd.md | 003 | | SSD | System Sequence Diagram | docs/uc-*/ssd.md | 003 |
@@ -24,7 +24,7 @@ document of a type. `Primary File` may contain a glob (e.g.
| DCD | Design Class Diagram | docs/dcd.md | 004 | | DCD | Design Class Diagram | docs/dcd.md | 004 |
| 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 | 031 | | RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 033 |
| TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 | | TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 |
## Languages ## Languages
+7 -6
View File
@@ -9,8 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-07 | Deprecated | Jens Tirsvad Nielsen | S02 | Added objective 11 (global command, project created in the current folder), a scope item and success criterion 11 | [1cd27f7] | | 2026-10-08 | Deprecated | Jens Tirsvad Nielsen | S02 | Objective 4: the local project has one remote, origin; no github remote, because a push to origin reaches GitHub through the mirror (MIL-008) | [039a28c] |
| 2026-10-07 | Accepted | Jens Tirsvad Nielsen | S02 | Objective 11, scope item and criterion 11: the configuration files default to the working folder's, then the checkout's | [0ab5006] | | 2026-10-08 | Accepted | Jens Tirsvad Nielsen | S02 | Objective 5 and scope: the installed `.claude`, `.agents` and `AGENTS.md` are excluded from git in the new project (MIL-009) | [08cb484] |
--- ---
@@ -37,8 +37,8 @@ One repeatable, reviewed procedure gives every new project the same secure basel
1. Optionally create an empty GitHub repository under a chosen user or organization. 1. Optionally create an empty GitHub repository under a chosen user or organization.
2. Create a Gitea repository under a chosen user or organization, empty, or with a license: the one set in `config.env` (`PROJECT_LICENSE`), or AGPL-3.0 when GitHub is chosen, the project is public and none is set. 2. Create a Gitea repository under a chosen user or organization, empty, or with a license: the one set in `config.env` (`PROJECT_LICENSE`), or AGPL-3.0 when GitHub is chosen, the project is public and none is set.
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 one remote, `origin` (Gitea), containing no credential. There is no `github` remote: a push to `origin` reaches GitHub through the push mirror.
5. Add the SQA-QC-Framework as the `framework` submodule with its own submodules (the `qc` checklists) fetched, 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 with its own submodules (the `qc` checklists) fetched, install its skills and git hooks, and copy its templates, optionally enabling the plan gate. Exclude the installed `.claude`, `.agents` and `AGENTS.md` from git in the new project, through its `.git/info/exclude`, so that no tracked file changes.
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. 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.
@@ -60,6 +60,7 @@ One repeatable, reviewed procedure gives every new project the same secure basel
- Partial-failure reporting with a documented way to continue. - Partial-failure reporting with a documented way to continue.
- Starting through a command link: the script finds its own files from the link, the new project lands in the folder it was started in, and `./config.env` and `./.env` there are read before the checkout's (confirmed before the first request). - Starting through a command link: the script finds its own files from the link, the new project lands in the folder it was started in, and `./config.env` and `./.env` there are read before the checkout's (confirmed before the first request).
- Fetching the framework's own submodules (`git submodule update --init --recursive`), so the `qc` checklists are present. - Fetching the framework's own submodules (`git submodule update --init --recursive`), so the `qc` checklists are present.
- Excluding `.claude`, `.agents` and `AGENTS.md` from git in the new project through its `.git/info/exclude`.
- 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`).
### Out of Scope ### Out of Scope
@@ -153,5 +154,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
[1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f [039a28c]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/039a28c01b56f8cf0af73f55d1a604b43d67ba03
[0ab5006]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/0ab50068bf9e5be82a801af9dbe5b763eeaf7f31 [08cb484]: https://git.tirsystem.com/TirSystem-BashScript/RepoFoundry/commit/08cb484498bab3d9480decda9df89e9564438185
+16 -12
View File
@@ -9,8 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-07 | Deprecated | Jens Tirsvad Nielsen | S02 | Default configuration files: --config and --env, else ./config.env and ./.env in the working folder, else the checkout's | [0ab5006] | | 2026-10-08 | Deprecated | Jens Tirsvad Nielsen | S02 | LocalProjectBuilder.build() no longer takes the GitHub repository; a Local Project has one Remote, origin (MIL-008) | [039a28c] |
| 2026-10-07 | Accepted | Jens Tirsvad Nielsen | S02 | .env is optional (as in UC-001 extension 2b): only config.env is required; a .env found nowhere means the token is asked | [24f1507] | | 2026-10-08 | Accepted | Jens Tirsvad Nielsen | S02 | FrameworkInstaller.excludeFromGit() and InstallResult.trackedPaths: `.claude`, `.agents` and `AGENTS.md` are excluded from git; UC-001 P15 (MIL-009) | [08cb484] |
--- ---
@@ -71,10 +71,11 @@ class GitHubClient <<facade>> {
+createEmptyRepository(request : ProjectRequest) : GitHubRepository +createEmptyRepository(request : ProjectRequest) : GitHubRepository
} }
class LocalProjectBuilder { class LocalProjectBuilder {
+build(directory : Path, source : GiteaRepository, target : GitHubRepository [0..1], sshPassed : Boolean) : LocalProject +build(directory : Path, source : GiteaRepository, sshPassed : Boolean) : LocalProject
} }
class FrameworkInstaller { class FrameworkInstaller {
+install(project : LocalProject, enablePlanGate : Boolean) : InstallResult +install(project : LocalProject, enablePlanGate : Boolean) : InstallResult
-excludeFromGit(project : LocalProject) : String [0..*]
} }
class SummaryReport { class SummaryReport {
+compose(request : ProjectRequest) : Summary +compose(request : ProjectRequest) : Summary
@@ -183,7 +184,9 @@ class Template {
-name : String -name : String
-isCopied : Boolean -isCopied : Boolean
} }
class InstallResult <<dto>> class InstallResult <<dto>> {
-trackedPaths : String [0..*]
}
class Summary { class Summary {
-createdItems : String [0..*] -createdItems : String [0..*]
-skippedItems : String [0..*] -skippedItems : String [0..*]
@@ -238,7 +241,7 @@ PushMirror "0..*" --> "1" GiteaRepository : source
PushMirror "0..*" --> "1" GitHubRepository : target PushMirror "0..*" --> "1" GitHubRepository : target
PushMirror "0..*" --> "1" Credential : authorised by PushMirror "0..*" --> "1" Credential : authorised by
LocalProject "1" *-- "1..2" Remote LocalProject "1" *-- "1" Remote
Remote "0..*" --> "1" Repository : points to Remote "0..*" --> "1" Repository : points to
LocalProject "1" *-- "1" Submodule LocalProject "1" *-- "1" Submodule
LocalProject "1" *-- "1" HookSetup LocalProject "1" *-- "1" HookSetup
@@ -272,7 +275,7 @@ Repository "0..*" --> "1" Visibility
| `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` | | `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` | | `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` | | `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` | | `FrameworkInstaller` | Framework, Framework Setup, Template | Adds the framework submodule, installs skills and hooks once, copies the templates without overwriting, and excludes `.claude`, `.agents` and `AGENTS.md` from git through `.git/info/exclude`. | none | `install`, `excludeFromGit` |
| `SummaryReport` | Summary | Composes the report of what was created, skipped or failed. | none | `compose` | | `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 | | `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 | | `Configuration` | Configuration | Holds the service addresses, the credentials and any preset project details. | `giteaUrl`, `giteaApiUrl`, `githubWebUrl`, `githubApiUrl`, `giteaSshPort`, `mirrorInterval`, `frameworkRepo`, `presetDetails` | none |
@@ -288,12 +291,12 @@ Repository "0..*" --> "1" Visibility
| `LicenseFile` | License | The license file in the Gitea repository when a license applies. | `key` | none | | `LicenseFile` | License | The license file in the Gitea repository when a license applies. | `key` | none |
| `PushMirror` | Mirror | The Gitea to GitHub push mirror. | `interval`, `syncOnCommit` | 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 | | `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 | | `Remote` | Remote | A named link to a repository (`origin`), without a credential. | `name`, `address` | none |
| `Submodule` | Framework | The framework added to the local project. | `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 | | `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 | | `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 | | `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 | | `InstallResult` | none (carries the result of one operation) | Returns the submodule, the hook setup and the templates of `install`, and the paths git already tracks that could not be excluded. | `trackedPaths` | none |
| `Summary` | Summary | The report returned to the Maintainer; it contains no credential. | `createdItems`, `skippedItems`, `nextSteps` | 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 | | `Visibility` | none (enumeration of a Project and Repository attribute) | The two allowed visibilities. | `private`, `public` | none |
@@ -321,8 +324,9 @@ Repository "0..*" --> "1" Visibility
| `GiteaClient.requestSync(mirror) : void` | [SD-001] `requestSync(pushMirror)`; P6 | | `GiteaClient.requestSync(mirror) : void` | [SD-001] `requestSync(pushMirror)`; P6 |
| `GitHubClient(configuration)` | [SD-001] `new(configuration)` to `GitHubClient` | | `GitHubClient(configuration)` | [SD-001] `new(configuration)` to `GitHubClient` |
| `GitHubClient.createEmptyRepository(request) : GitHubRepository` | [SD-001] `createEmptyRepository(request)`; P5 | | `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 | | `LocalProjectBuilder.build(directory, source, sshPassed) : LocalProject` | [SD-001] `build(directory, giteaRepository, sshPassed)`; P7, P8, P9 |
| `FrameworkInstaller.install(project, enablePlanGate) : InstallResult` | [SD-001] `install(localProject, enablePlanGate)`; P10, P11, P12 | | `FrameworkInstaller.install(project, enablePlanGate) : InstallResult` | [SD-001] `install(localProject, enablePlanGate)`; P10, P11, P12 |
| `FrameworkInstaller.excludeFromGit(project) : String [0..*]` | [SD-001] `excludeFromGit(localProject)`; P15 |
| `SummaryReport.compose(request) : Summary` | [SD-001] `compose(projectRequest)`; P13 | | `SummaryReport.compose(request) : Summary` | [SD-001] `compose(projectRequest)`; P13 |
## Pattern Annotations ## Pattern Annotations
@@ -334,7 +338,7 @@ Repository "0..*" --> "1" Visibility
| Pure Fabrication (GRASP) | `ConfigLoader`, `ToolChecker`, `CredentialCollector`, `EnvFileWriter`, `Preflight`, `LocalProjectBuilder`, `FrameworkInstaller`, `SummaryReport` | No domain concept owns these responsibilities; small units keep cohesion high | | 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 | | 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 | | 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 | | Data Transfer Object (GoF-style) | `InstallResult` | Carries the results of `install` (the submodule, the hook setup, the templates and the tracked paths) in one return value |
## Dependency Check ## Dependency Check
@@ -373,5 +377,5 @@ SOLID check: no class has more than one reason to change (one host API, one kind
[SD-001]: ./uc-001/sd.md [SD-001]: ./uc-001/sd.md
[MIL-005]: ./milestones/mil-005-credentials.md [MIL-005]: ./milestones/mil-005-credentials.md
[DICT-001]: ./dictionary.md [DICT-001]: ./dictionary.md
[0ab5006]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/0ab50068bf9e5be82a801af9dbe5b763eeaf7f31 [039a28c]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/039a28c01b56f8cf0af73f55d1a604b43d67ba03
[24f1507]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/24f15070fc73fb06e61865141fe0b825ea9e821e [08cb484]: https://git.tirsystem.com/TirSystem-BashScript/RepoFoundry/commit/08cb484498bab3d9480decda9df89e9564438185
+5 -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-07 | Deprecated | Jens Tirsvad Nielsen | S02 | Added Command Link, Checkout and Working Folder (DM-003, UC-002) | [1cd27f7] | | 2026-10-07 | Deprecated | Jens Tirsvad Nielsen | S02 | `ConfigFiles` named as a system concept without a PO term | [0ab5006] |
| 2026-10-07 | Accepted | Jens Tirsvad Nielsen | S02 | `ConfigFiles` named as a system concept without a PO term | [0ab5006] | | 2026-10-08 | Accepted | Jens Tirsvad Nielsen | S02 | Framework Setup and Template: the folders that hold the skills and the copy of AGENTS.md are ignored by git (MIL-009) | [08cb484] |
--- ---
@@ -36,8 +36,8 @@ Maps each Product Owner (PO) term to its professional IT term. PO language: Engl
| Local Project | en | LocalProject | The project directory on the Maintainer's machine. | DM, UC | OC, SD, DCD | | Local Project | en | LocalProject | The project directory on the Maintainer's machine. | DM, UC | OC, SD, DCD |
| Remote | en | Remote | A named link from a local project to a repository. | DM, UC | OC, SD, DCD | | Remote | en | Remote | A named link from a local project to a repository. | DM, UC | OC, SD, DCD |
| Framework | en | Submodule | The SQA-QC-Framework added to a local project; the IT term names how it is attached. | DM, UC | OC, SD, DCD | | Framework | en | Submodule | The SQA-QC-Framework added to a local project; the IT term names how it is attached. | DM, UC | OC, SD, DCD |
| Framework Setup | en | HookSetup | The skills and git hooks installed from the framework, with the plan gate on or off. | DM, UC | OC, SD, DCD | | Framework Setup | en | HookSetup | The skills and git hooks installed from the framework, with the plan gate on or off; the folders that hold the skills are ignored by git. | DM, UC | OC, SD, DCD |
| Template | en | Template | A framework file copied into a project. | DM, UC | OC, SD, DCD | | Template | en | Template | A framework file copied into a project; the copy of `AGENTS.md` is ignored by git. | DM, UC | OC, SD, DCD |
| 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 | | 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 |
| Command Link | en | CommandLink | A name on the shell's search path that leads to the script; made by the Maintainer, only followed by the system (no design class). | DM, UC | OC, SD, DCD | | Command Link | en | CommandLink | A name on the shell's search path that leads to the script; made by the Maintainer, only followed by the system (no design class). | DM, UC | OC, SD, DCD |
| Checkout | en | Checkout | The folder that holds RepoFoundry and its default `config.env` and `.env`. | DM, UC | OC, SD, DCD | | Checkout | en | Checkout | The folder that holds RepoFoundry and its default `config.env` and `.env`. | DM, UC | OC, SD, DCD |
@@ -68,5 +68,5 @@ Maps each Product Owner (PO) term to its professional IT term. PO language: Engl
[DM-002]: ./domain-model.md [DM-002]: ./domain-model.md
[OC-001]: ./uc-001/oc.md [OC-001]: ./uc-001/oc.md
[DCD-001]: ./uc-001/dcd.md [DCD-001]: ./uc-001/dcd.md
[1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f
[0ab5006]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/0ab50068bf9e5be82a801af9dbe5b763eeaf7f31 [0ab5006]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/0ab50068bf9e5be82a801af9dbe5b763eeaf7f31
[08cb484]: https://git.tirsystem.com/TirSystem-BashScript/RepoFoundry/commit/08cb484498bab3d9480decda9df89e9564438185
+9 -9
View File
@@ -9,8 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-07 | Deprecated | Jens Tirsvad Nielsen | S02 | Added Command Link, Checkout and Working Folder (from DM-003, UC-002) | [1cd27f7] | | 2026-10-08 | Deprecated | Jens Tirsvad Nielsen | S02 | A Local Project has one Remote, origin, no longer one or two (MIL-008) | [039a28c] |
| 2026-10-07 | Accepted | Jens Tirsvad Nielsen | S02 | Default configuration files: --config and --env, else ./config.env and ./.env in the working folder, else the checkout's | [0ab5006] | | 2026-10-08 | Accepted | Jens Tirsvad Nielsen | S02 | Framework Setup and Template: the folders that hold the skills and the copy of AGENTS.md are ignored by git (UC-001, MIL-009) | [08cb484] |
--- ---
@@ -114,7 +114,7 @@ Mirror "1" --> "1" GiteaRepository : copies from
Mirror "1" --> "1" GitHubRepository : copies to Mirror "1" --> "1" GitHubRepository : copies to
Mirror "0..*" --> "1" AccessToken : is authorised by Mirror "0..*" --> "1" AccessToken : is authorised by
LocalProject "1" --> "1" Project : is the working copy of LocalProject "1" --> "1" Project : is the working copy of
LocalProject "1" --> "1..2" Remote : has LocalProject "1" --> "1" Remote : has
Remote "0..*" --> "1" Repository : points to Remote "0..*" --> "1" Repository : points to
LocalProject "1" --> "1" Framework : includes LocalProject "1" --> "1" Framework : includes
LocalProject "1" --> "1" FrameworkSetup : has LocalProject "1" --> "1" FrameworkSetup : has
@@ -149,10 +149,10 @@ WorkingFolder "1" --> "0..*" LocalProject : is the base of
| License | The legal terms file added to a Gitea Repository when a license applies: the one set in the Configuration, or AGPL-3.0 when GitHub is chosen, the project is public and none is set | name | [UC-001] step 6 "license" | | License | The legal terms file added to a Gitea Repository when a license applies: the one set in the Configuration, or AGPL-3.0 when GitHub is chosen, the project is public and none is set | name | [UC-001] step 6 "license" |
| Mirror | The push mirror that copies a Gitea Repository to a GitHub Repository | interval, sync on commit | [UC-001] step 7 "push mirror" | | Mirror | The push mirror that copies a Gitea Repository to a GitHub Repository | interval, sync on commit | [UC-001] step 7 "push mirror" |
| Local Project | The project directory on the Maintainer's machine | directory | [UC-001] step 8 "local project" | | Local Project | The project directory on the Maintainer's machine | directory | [UC-001] step 8 "local project" |
| Remote | A named link from a Local Project to a Repository (`origin`, `github`) | name, address | [UC-001] step 8 "remote" | | Remote | A named link from a Local Project to a Repository (`origin`) | name, address | [UC-001] step 8 "remote" |
| 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; the folders that hold the skills (`.claude`, `.agents`) are ignored by git | plan gate enabled | [UC-001] step 9 "skills and hooks", "plan gate", "excludes from git" |
| 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); the copy of `AGENTS.md` is ignored by git | name | [UC-001] step 9 "templates", "excludes from git" |
| 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" | | 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" |
| Command Link | A name in a folder on the shell's search path that leads to the script in the Checkout | name, folder | [UC-002] step 1 "command link" | | Command Link | A name in a folder on the shell's search path that leads to the script in the Checkout | name, folder | [UC-002] step 1 "command link" |
| Checkout | The folder that holds RepoFoundry: the script, its own files and by default `config.env` and `.env` | path | [UC-002] step 4 "checkout" | | Checkout | The folder that holds RepoFoundry: the script, its own files and by default `config.env` and `.env` | path | [UC-002] step 4 "checkout" |
@@ -176,7 +176,7 @@ WorkingFolder "1" --> "0..*" LocalProject : is the base of
| Mirror | copies to | GitHub Repository | 1 to 1 | | Mirror | copies to | GitHub Repository | 1 to 1 |
| Mirror | is authorised by | Access Token | 0..* to 1 | | Mirror | is authorised by | Access Token | 0..* to 1 |
| Local Project | is the working copy of | Project | 1 to 1 | | Local Project | is the working copy of | Project | 1 to 1 |
| Local Project | has | Remote | 1 to 1..2 | | Local Project | has | Remote | 1 to 1 |
| Remote | points to | Repository | 0..* to 1 | | Remote | points to | Repository | 0..* to 1 |
| Local Project | includes | Framework | 1 to 1 | | Local Project | includes | Framework | 1 to 1 |
| Local Project | has | Framework Setup | 1 to 1 | | Local Project | has | Framework Setup | 1 to 1 |
@@ -207,5 +207,5 @@ WorkingFolder "1" --> "0..*" LocalProject : is the base of
[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
[1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f [039a28c]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/039a28c01b56f8cf0af73f55d1a604b43d67ba03
[0ab5006]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/0ab50068bf9e5be82a801af9dbe5b763eeaf7f31 [08cb484]: https://git.tirsystem.com/TirSystem-BashScript/RepoFoundry/commit/08cb484498bab3d9480decda9df89e9564438185
@@ -9,8 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-05 | Deprecated | Jens Tirsvad Nielsen | S02 | Initial version | [424f14f] | | 2026-10-05 | Deprecated | Jens Tirsvad Nielsen | S02 | Optional GitHub; choosing GitHub applies the AGPL license to the Gitea repository<br>Cited US-001.03<br>Traces to objective 7<br>Criterion 2 names the framework URL<br>Target date accepted | [02875ae] |
| 2026-10-05 | Accepted | Jens Tirsvad Nielsen | S02 | Optional GitHub; choosing GitHub applies the AGPL license to the Gitea repository<br>Cited US-001.03<br>Traces to objective 7<br>Criterion 2 names the framework URL<br>Target date accepted | [02875ae] | | 2026-10-08 | Accepted | Jens Tirsvad Nielsen | S02 | Criterion 1 and task 1: origin is the only remote (MIL-008) | [039a28c] |
--- ---
@@ -26,7 +26,7 @@ Complete `create-project.sh` (local directory, credential-free remotes, framewor
| # | Criterion (objectively checkable) | Go | No-Go | | # | Criterion (objectively checkable) | Go | No-Go |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| 1 | `git remote -v` shows `origin` (Gitea) and, when GitHub was chosen, `github`, with no credentials in any URL; with GitHub chosen the local history contains the license commit | Verified | Any credential, or a missing license commit | | 1 | `git remote -v` shows `origin` (Gitea) only, with or without GitHub, with no credentials in any URL; with GitHub chosen the local history contains the license commit | Verified | Any credential, or a missing license commit |
| 2 | `framework` is a submodule of `ssh://git@git.tirsystem.com:10022/TirSystem/SQA-QC-Framework.git` and the install scripts have run once, in the documented order | Verified | Missing or repeated | | 2 | `framework` is a submodule of `ssh://git@git.tirsystem.com:10022/TirSystem/SQA-QC-Framework.git` and the install scripts have run once, in the documented order | Verified | Missing or repeated |
| 3 | An existing directory, `AGENTS.md` or `docs/artifact-registry.md` is never overwritten without a yes | Verified by a second run | Overwritten | | 3 | An existing directory, `AGENTS.md` or `docs/artifact-registry.md` is never overwritten without a yes | Verified by a second run | Overwritten |
| 4 | With the plan gate enabled, a commit touching `src/` or `tests/` without a `Task: MIL-NNN#N` trailer is refused | Verified | Accepted | | 4 | With the plan gate enabled, a commit touching `src/` or `tests/` without a `Task: MIL-NNN#N` trailer is refused | Verified | Accepted |
@@ -64,7 +64,7 @@ Complete `create-project.sh` (local directory, credential-free remotes, framewor
| # | Task | Summary | Needs its own Use Case/User Story? | Reference | | # | Task | Summary | Needs its own Use Case/User Story? | Reference |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| 1 | Create the local project directory and credential-free remotes | After consent, create the directory (refuse to reuse an existing one without a yes), run `git init` on `main`, and add `origin` (Gitea) and, only if GitHub was chosen, `github` using URLs derived from the configured base URLs and the selected owners, with no token in any URL. `origin` uses HTTPS derived from `GITEA_URL`, unless the SSH test from the preflight passed, in which case it uses SSH on port 10022. When the Gitea repository is not empty (GitHub chosen, AGPL license), fetch it and check out its default branch so the local history starts from the license commit. Do not make a commit. | Yes | [UC-001] | | 1 | Create the local project directory and credential-free remotes | After consent, create the directory (refuse to reuse an existing one without a yes), run `git init` on `main`, and add `origin` (Gitea) using a URL derived from the configured base URL and the selected owner, with no token in any URL; there is no `github` remote, because a push to `origin` reaches GitHub through the mirror ([MIL-008]). `origin` uses HTTPS derived from `GITEA_URL`, unless the SSH test from the preflight passed, in which case it uses SSH on port 10022. When the Gitea repository is not empty (GitHub chosen, AGPL license), fetch it and check out its default branch so the local history starts from the license commit. Do not make a commit. | Yes | [UC-001] |
| 2 | Add the framework submodule | From the project directory run `git submodule add ssh://git@git.tirsystem.com:10022/TirSystem/SQA-QC-Framework.git framework`. Check beforehand that SSH on port 10022 works and stop with an actionable message if not. Document that this SSH access must be configured. | Yes | [UC-001] | | 2 | Add the framework submodule | From the project directory run `git submodule add ssh://git@git.tirsystem.com:10022/TirSystem/SQA-QC-Framework.git framework`. Check beforehand that SSH on port 10022 works and stop with an actionable message if not. Document that this SSH access must be configured. | Yes | [UC-001] |
| 3 | Install skills and git hooks, with optional plan gate | Run `framework/scripts/install-skills.sh` and `install-git-hooks.sh`. The hook installer only sets `core.hooksPath` to `framework/githooks` and is safe to rerun, but it would replace a different existing value, so read the current value first and ask. Offer `--enable-plan-gate` as an optional choice, which requires a `Task: MIL-NNN#N` trailer on commits changing `src/` or `tests/`. | Yes | [UC-001] | | 3 | Install skills and git hooks, with optional plan gate | Run `framework/scripts/install-skills.sh` and `install-git-hooks.sh`. The hook installer only sets `core.hooksPath` to `framework/githooks` and is safe to rerun, but it would replace a different existing value, so read the current value first and ask. Offer `--enable-plan-gate` as an optional choice, which requires a `Task: MIL-NNN#N` trailer on commits changing `src/` or `tests/`. | Yes | [UC-001] |
| 4 | Copy the framework templates without overwriting | Copy `AGENTS-template.md` to `AGENTS.md` and `artifact-registry-template.md` to `docs/artifact-registry.md` after `mkdir -p docs`, asking before replacing an existing file. | Yes | [UC-001] | | 4 | Copy the framework templates without overwriting | Copy `AGENTS-template.md` to `AGENTS.md` and `artifact-registry-template.md` to `docs/artifact-registry.md` after `mkdir -p docs`, asking before replacing an existing file. | Yes | [UC-001] |
@@ -77,5 +77,6 @@ Complete `create-project.sh` (local directory, credential-free remotes, framewor
[US-001]: ../user-stories.md [US-001]: ../user-stories.md
[UC-001]: ../uc-001/uc.md [UC-001]: ../uc-001/uc.md
[MIL-002]: ./mil-002-repositories-and-mirror.md [MIL-002]: ./mil-002-repositories-and-mirror.md
[424f14f]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/424f14f4f5577bb47fea41c8f3a655dca953e6d8 [MIL-008]: ./mil-008-gitea-only-remote.md
[02875ae]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/02875aee5f2953473924074eea0056eb31af6b7a [02875ae]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/02875aee5f2953473924074eea0056eb31af6b7a
[039a28c]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/039a28c01b56f8cf0af73f55d1a604b43d67ba03
@@ -0,0 +1,85 @@
# MIL-008 Gitea Is the Only Remote
## Metadata
| Key | Value |
| --- | --- |
| ID | MIL-008 |
| CrossReference | [BC-001], [US-001], [UC-001], [OC-001], [DCD-002] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-08 | Deprecated | Jens Tirsvad Nielsen | S02 | Initial version | [039a28c] |
| 2026-10-08 | Accepted | Jens Tirsvad Nielsen | S02 | Task 4 and criterion 7: the version is raised to 0.3.1 and release v0.3.1 is tagged after the merge | [1056639] |
---
## Purpose
Decide whether the local project can keep a single remote, `origin` (Gitea), with or without GitHub. Gitea pushes to GitHub through the push mirror, so a push to `origin` already reaches GitHub; a second `github` remote adds nothing and invites a push that goes around the mirror, with a token typed by hand. The first version of the documents and of the script added that remote when GitHub was chosen; this phase removes it.
## Deliverable
`create-project.sh` that adds only the `origin` remote to the local project, whether or not GitHub was chosen, and no longer builds a GitHub remote address. The documents agree with it: Business Case objective 4, US-001.03, UC-001 (postcondition and step 8), OC-001 P9, SD-001, DCD-001 and DCD-002, DM-001 and DM-002. `README.md` describes the one remote and says that a push to `origin` reaches GitHub through the mirror. The tests cover both cases. The version is raised to 0.3.1, and release `v0.3.1` is tagged on Gitea after the pull request is merged.
A project that already has a `github` remote, such as one created by an earlier version of the script, keeps it: the script never removes or replaces a remote (it still refuses an `origin` that points elsewhere).
## Go / No-Go Criteria
| # | Criterion (objectively checkable) | Go | No-Go |
| --- | --- | --- | --- |
| 1 | With GitHub chosen, `git remote` in the new project lists exactly `origin`; without GitHub it lists exactly `origin` as well | Tests pass | Any other remote |
| 2 | No `remote.github.*` key and no GitHub address is in the new project's `.git/config`, and `origin` keeps its address (SSH if the test passed, else HTTPS) with no credential | Tests pass | A `github` key, a GitHub address or a credential |
| 3 | A project directory that already has a `github` remote keeps it unchanged and the run does not fail because of it; a different `origin` is still refused | Tests pass | The remote removed, replaced or the run stopped |
| 4 | With GitHub chosen the mirror, the license history and the other steps are unchanged | Tests pass | Any other step changed |
| 5 | The README, UC-001, OC-001, SD-001, DCD-001, DCD-002, DM-001 and DM-002 all describe one remote and agree with the code | Reviewed by S02 in [RC-031] | A document still naming a `github` remote |
| 6 | All acceptance criteria of US-001.03 in [US-001] are met | Verified | Any unmet |
| 7 | `create-project.sh --version` prints `RepoFoundry 0.3.1` | Tests pass | Another version |
## Dependencies
| Depends on | Reason |
| --- | --- |
| [MIL-002] | The push mirror is what makes a second remote unnecessary |
| [MIL-003] | The step that creates the local project and its remotes is the one that changes |
## Traceability
| Business Case objective / KPI / user story | Reference |
| --- | --- |
| User story US-001.03 | [US-001] |
| Objective 4 (the local project with its remotes) | [BC-001] |
| Success criteria 1 (credential exposure: fewer places a token can be typed into an address) and 3 (mirror direction: a push to `origin` appears on GitHub) | [BC-001] |
## Ownership
| Role | Stakeholder ID (SA) |
| --- | --- |
| Owner | S01 |
| Approving reviewer | S02 |
## Target Date
2026-12-18 — proposed; the Business Case sets no deadline.
## Tasks
| # | Task | Summary | Needs its own Use Case/User Story? | Reference |
| --- | --- | --- | --- | --- |
| 1 | Add only the origin remote | In `create_local_project` (`src/lib/localproject.sh`) stop adding the `github` remote, with or without GitHub, and delete `github_remote_url` (`src/lib/hosts.sh`), which nothing else uses. `origin` is unchanged (SSH on the configured port when the SSH test passed, else HTTPS, no credential). An existing `github` remote is left alone; a different `origin` is still refused. Step 8 of [UC-001] and P9 of [OC-001]. | Yes | [UC-001] |
| 2 | Describe the one remote in the README | Update the overview, the run steps and the "Credential-free remotes" security point: the project has one remote, `origin`, and a push to it reaches GitHub through the Gitea push mirror. Mention that a `github` remote from an earlier version can be removed with `git remote remove github`. | No | |
| 3 | Test the single remote | Replace the `github` remote assertion of `test_local_project_gets_credential_free_remotes_and_the_license_history` with "only `origin`, no GitHub address in `.git/config`", keep the Gitea-only case, add a rerun on a directory that already has a `github` remote (kept, no failure), and drop the `github_remote_url` check from `test_remote_addresses_are_built_from_the_configuration`. | No | |
| 4 | Bump the version to 0.3.1 | Set `VERSION` in `src/lib/constants.sh` to 0.3.1 and the `--version` check in `tests/test-security.sh` to match. Release `v0.3.1` is tagged on Gitea from the merge commit once the pull request is merged. | No | |
---
[BC-001]: ../business-case.md
[US-001]: ../user-stories.md
[UC-001]: ../uc-001/uc.md
[OC-001]: ../uc-001/oc.md
[DCD-002]: ../dcd.md
[MIL-002]: ./mil-002-repositories-and-mirror.md
[MIL-003]: ./mil-003-scaffold-and-release.md
[RC-031]: ../sqa/reviews/rc-031-gitea-only-remote.md
[039a28c]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/039a28c01b56f8cf0af73f55d1a604b43d67ba03
[1056639]: https://git.tirsystem.com/TirSystem-BashScript/RepoFoundry/commit/1056639d5b0f84ce8b591e33e8f5a1e03e99de37
@@ -0,0 +1,86 @@
# MIL-009 Framework Files Excluded from Git
## Metadata
| Key | Value |
| --- | --- |
| ID | MIL-009 |
| CrossReference | [BC-001], [US-001], [UC-001], [OC-001], [DCD-002] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-08 | Accepted | Jens Tirsvad Nielsen | S02 | Initial version | [08cb484] |
---
## Purpose
Decide whether the files the framework installs into a new project, `.claude`, `.agents` and `AGENTS.md`, stay out of that project's git history. The skills in `.claude` and `.agents` are copies made by the framework's installer, and `AGENTS.md` is copied from a framework template. Git should not list them as untracked or offer them to a commit. They are excluded the way the project's `.env` already is: in `.git/info/exclude`, which belongs to the clone, is never committed and changes no tracked file.
## Deliverable
`create-project.sh` that, once the framework's skills and the templates are in place, adds `/.claude`, `/.agents` and `/AGENTS.md` to `.git/info/exclude` of the new project, as its own step ("Git excludes") that shows in the dry-run plan and in the summary. The documents agree with it: Business Case objective 5, US-001.03, UC-001 (postcondition, step 9, extension 9f and a rule), OC-001 (P15 and two exceptions), SD-001, DCD-001 and DCD-002, DM-001 and DM-002, and the dictionary. `README.md` says which files are excluded and why, and how to track one anyway. The tests cover the cases below. The version is raised to 0.3.2, and release `v0.3.2` is tagged on Gitea after the pull request is merged.
The exclusion is not applied when the framework steps are skipped (no SSH to Gitea): nothing was installed. A path that git already tracks stays tracked: the script never runs `git rm`, and the summary names the path, because an exclude entry does not apply to a tracked file. `framework`, `.gitmodules` and `docs/artifact-registry.md` are not excluded; the submodule and the registry are part of the project. The files of this repository (RepoFoundry itself) are unchanged.
## Go / No-Go Criteria
| # | Criterion (objectively checkable) | Go | No-Go |
| --- | --- | --- | --- |
| 1 | After a run with the framework steps, `git check-ignore` reports `.claude`, `.agents` and `AGENTS.md` as ignored in the new project, and `git status --porcelain` lists none of them although they exist | Tests pass | Any of the three listed or not ignored |
| 2 | The entries are written only to `.git/info/exclude`: no `.gitignore` is created or changed, no tracked file changes and nothing is committed | Tests pass | Any other file changed |
| 3 | A second run writes no entry twice, keeps the existing lines of `.git/info/exclude` each on its own line (also when the file has no final newline), and writes nothing for a path that is already ignored | Tests pass | A duplicate, a merged line or a lost line |
| 4 | A path that git already tracks is neither untracked nor changed, and the summary names it as not ignored | Tests pass | A path untracked, or the summary silent |
| 5 | With the framework steps skipped (no SSH to Gitea) nothing is added to `.git/info/exclude` and the step reports `skipped` | Tests pass | An entry written |
| 6 | `framework`, `.gitmodules` and `docs/artifact-registry.md` are not ignored in the new project | Tests pass | Any of them ignored |
| 7 | The dry run lists the "Git excludes" step with the three entries and changes nothing; the summary reports the step in the same words | Tests pass | A change in a dry run, or the step missing |
| 8 | The `.env` exclusion behaves as before: added only after the yes, once, with the same comment line | Tests pass | A changed result |
| 9 | The README, Business Case, UC-001, OC-001, SD-001, DCD-001, DCD-002, DM-001 and DM-002 describe the exclusion and agree with the code; the documents the model does not change say so | Reviewed by S02 | A document that contradicts the code |
| 10 | All acceptance criteria of US-001.03 in [US-001] are met | Verified | Any unmet |
| 11 | `create-project.sh --version` prints `RepoFoundry 0.3.2` | Tests pass | Another version |
## Dependencies
| Depends on | Reason |
| --- | --- |
| [MIL-003] | The framework, skills and templates steps are the ones that install the files |
| [MIL-005] | The `.git/info/exclude` code written for the project's `.env` is reused |
## Traceability
| Business Case objective / KPI / user story | Reference |
| --- | --- |
| User story US-001.03 | [US-001] |
| Objective 5 (the framework, its skills and its templates in the new project) | [BC-001] |
## Ownership
| Role | Stakeholder ID (SA) |
| --- | --- |
| Owner | S01 |
| Approving reviewer | S02 |
## Target Date
2026-12-23 — proposed; the Business Case sets no deadline.
## Tasks
| # | Task | Summary | Needs its own Use Case/User Story? | Reference |
| --- | --- | --- | --- | --- |
| 1 | Update the analysis and design documents | Business Case objective 5 and scope, the acceptance criteria of US-001.03, UC-001 (postcondition, step 9, extension 9f for a tracked path, and a rule), OC-001 (postcondition P15 and two exceptions), SD-001 (`excludeFromGit` inside `install`), DCD-001 and DCD-002 (`FrameworkInstaller.excludeFromGit`, `InstallResult.trackedPaths`), and the definitions of Framework Setup and Template in DM-001, DM-002 and the dictionary. | No | |
| 2 | Add the Git excludes step | Move the work of `exclude_env_file` (`src/lib/envfile.sh`) into a helper that adds a list of entries to `.git/info/exclude`: one comment line, a fresh line first, no entry that `git check-ignore` already reports, a check afterwards. `exclude_env_file` keeps its behavior and uses it. Add a function in `src/lib/framework.sh` that excludes `/.claude`, `/.agents` and `/AGENTS.md`, called from `create_all` (`src/lib/apply.sh`) after `copy_templates`, with its own label in `PLAN_STEPS` (`src/lib/constants.sh`) and a line in the dry-run plan (`src/lib/plan.sh`). It is skipped when `is_framework_skipped` and it names any path git tracks. Step 9 of [UC-001] and P12 of [OC-001]. | Yes | [UC-001] |
| 3 | Describe the excluded files in the README | Add the step to the overview and the numbered run steps, and the new line to the sample plan output. Say which paths are excluded and why (they come from the framework and are made again by `bash framework/scripts/install-skills.sh`), that nothing is committed or changed in a tracked file, that the entries live in `.git/info/exclude` and so are not shared with a clone, and how to track one anyway (remove its line from `.git/info/exclude`). | No | |
| 4 | Test the excludes | In the style of `tests/test-credentials.sh`: the three paths ignored and absent from `git status` after a run; a second run adds nothing; a file without a final newline keeps its lines; a tracked `AGENTS.md` stays tracked and is named; framework steps skipped writes nothing; `framework`, `.gitmodules` and `docs/artifact-registry.md` not ignored; the dry run changes nothing; the existing `.env` exclude tests still pass. | No | |
| 5 | Bump the version to 0.3.2 | Set `VERSION` in `src/lib/constants.sh` to 0.3.2 and the `--version` check in `tests/test-security.sh` to match. Release `v0.3.2` is tagged on Gitea from the merge commit once the pull request is merged. | No | |
---
[BC-001]: ../business-case.md
[US-001]: ../user-stories.md
[UC-001]: ../uc-001/uc.md
[OC-001]: ../uc-001/oc.md
[DCD-002]: ../dcd.md
[MIL-003]: ./mil-003-scaffold-and-release.md
[MIL-005]: ./mil-005-credentials.md
[08cb484]: https://git.tirsystem.com/TirSystem-BashScript/RepoFoundry/commit/08cb484498bab3d9480decda9df89e9564438185
+18 -8
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], [MIL-005], [MIL-006], [MIL-007], [US-001] | | CrossReference | [BC-001], [SA-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007], [MIL-008], [MIL-009], [US-001] |
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-06 | Deprecated | Jens Tirsvad Nielsen | S02 | Added phase MIL-006 (proposed dates 2026-11-30 to 2026-12-04) | [d773fa9] | | 2026-10-08 | Deprecated | Jens Tirsvad Nielsen | S02 | Added phase MIL-008 (proposed dates 2026-12-14 to 2026-12-18): the local project has origin as its only remote | [039a28c] |
| 2026-10-07 | Accepted | Jens Tirsvad Nielsen | S02 | Added phase MIL-007 (proposed dates 2026-12-07 to 2026-12-11); MIL-006 deliverable names the public-only AGPL-3.0 default | [1cd27f7] | | 2026-10-08 | Accepted | Jens Tirsvad Nielsen | S02 | Added phase MIL-009 (proposed dates 2026-12-21 to 2026-12-23): the framework files .claude, .agents and AGENTS.md are excluded from git | [08cb484] |
--- ---
## Purpose ## Purpose
Schedule the seven 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 nine 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-12-11 (the last two phases are proposed). - Week 1 starts 2026-10-05; the plan ends by 2026-12-23 (the last four phases are 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.
@@ -36,6 +36,8 @@ Schedule the seven phases that deliver RepoFoundry (`create-project.sh` and its
| Credentials | [MIL-005] | 2026-11-23 to 2026-11-27 | 2026-11-27 | S01 | US-001.05 | Missing credentials asked; project .env | | | Credentials | [MIL-005] | 2026-11-23 to 2026-11-27 | 2026-11-27 | S01 | US-001.05 | Missing credentials asked; project .env | |
| Project License | [MIL-006] | 2026-11-30 to 2026-12-04 | 2026-12-04 | S01 | US-001.06 | PROJECT_LICENSE in config.env; AGPL-3.0 default only for a public GitHub project | | | Project License | [MIL-006] | 2026-11-30 to 2026-12-04 | 2026-12-04 | S01 | US-001.06 | PROJECT_LICENSE in config.env; AGPL-3.0 default only for a public GitHub project | |
| Framework Checklists and Usage | [MIL-007] | 2026-12-07 to 2026-12-11 | 2026-12-11 | S01 | US-001.07 | qc fetched with the framework; global command; README usage | | | Framework Checklists and Usage | [MIL-007] | 2026-12-07 to 2026-12-11 | 2026-12-11 | S01 | US-001.07 | qc fetched with the framework; global command; README usage | |
| Gitea Is the Only Remote | [MIL-008] | 2026-12-14 to 2026-12-18 | 2026-12-18 | S01 | US-001.03 | The local project has `origin` only; GitHub is reached through the mirror | |
| Framework Files Excluded from Git | [MIL-009] | 2026-12-21 to 2026-12-23 | 2026-12-23 | S01 | US-001.03 | `.claude`, `.agents` and `AGENTS.md` excluded from git in the new project | |
```plantuml ```plantuml
@startgantt @startgantt
@@ -54,6 +56,10 @@ Project starts 2026-10-05
[Project License Go/No-Go] happens 2026-12-04 [Project License Go/No-Go] happens 2026-12-04
[Framework Checklists and Usage] starts 2026-12-07 and ends 2026-12-11 [Framework Checklists and Usage] starts 2026-12-07 and ends 2026-12-11
[Framework Checklists and Usage Go/No-Go] happens 2026-12-11 [Framework Checklists and Usage Go/No-Go] happens 2026-12-11
[Gitea Is the Only Remote] starts 2026-12-14 and ends 2026-12-18
[Gitea Is the Only Remote Go/No-Go] happens 2026-12-18
[Framework Files Excluded from Git] starts 2026-12-21 and ends 2026-12-23
[Framework Files Excluded from Git Go/No-Go] happens 2026-12-23
@endgantt @endgantt
``` ```
@@ -72,11 +78,13 @@ Project starts 2026-10-05
| Project license set in `config.env` | [MIL-006] | | Project license set in `config.env` | [MIL-006] |
| Framework's own submodules fetched | [MIL-007] | | Framework's own submodules fetched | [MIL-007] |
| README usage from the target folder and as a global command | [MIL-007] | | README usage from the target folder and as a global command | [MIL-007] |
| The local project has `origin` as its only remote | [MIL-008] |
| `.claude`, `.agents` and `AGENTS.md` excluded from git in the new project | [MIL-009] |
## Dependencies ## Dependencies
``` ```
MIL-001 → MIL-002 → MIL-003 → MIL-004 → MIL-005 → MIL-006 → MIL-007 MIL-001 → MIL-002 → MIL-003 → MIL-004 → MIL-005 → MIL-006 → MIL-007 → MIL-008 → MIL-009
``` ```
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.
@@ -107,11 +115,13 @@ A No-Go moves every later date by the time needed to rework the failed criteria.
[MIL-005]: ./milestones/mil-005-credentials.md [MIL-005]: ./milestones/mil-005-credentials.md
[MIL-006]: ./milestones/mil-006-project-license.md [MIL-006]: ./milestones/mil-006-project-license.md
[MIL-007]: ./milestones/mil-007-framework-checklists.md [MIL-007]: ./milestones/mil-007-framework-checklists.md
[MIL-008]: ./milestones/mil-008-gitea-only-remote.md
[MIL-009]: ./milestones/mil-009-exclude-framework-files.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
[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
[d773fa9]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/d773fa91df5a54090254e12e074880fb6526a9ff [039a28c]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/039a28c01b56f8cf0af73f55d1a604b43d67ba03
[1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f [08cb484]: https://git.tirsystem.com/TirSystem-BashScript/RepoFoundry/commit/08cb484498bab3d9480decda9df89e9564438185
@@ -0,0 +1,80 @@
# SQA Review Record: Gitea is the only remote
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-031 |
| CrossReference | [MIL-008], [QC-MIL-001], [MIL-003], [BC-001], [US-001], [UC-001], [OC-001], [SD-001], [DCD-001], [DCD-002], [DM-001], [DM-002], [PP-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-08 | Deprecated | Jens Tirsvad Nielsen | S02 | Initial version | [039a28c] |
| 2026-10-08 | Accepted | Jens Tirsvad Nielsen | S02 | Criterion count of MIL-008 is seven (the version criterion) | [1056639] |
---
## Artifact Under Review
- Instance reviewed: [MIL-008], and the changes it causes in [MIL-003], [BC-001], [US-001], [UC-001], [OC-001], [SD-001], [DCD-001], [DCD-002], [DM-001], [DM-002], [PP-001] and [TM-001].
- Why: a project created by the script had two remotes, `origin` (Gitea) and `github`. The documents required the second one when GitHub was chosen (objective 4, US-001.03, step 8 of [UC-001], P9 of [OC-001]). Gitea already pushes to GitHub through the push mirror, so the second remote is not needed and invites a push that goes around the mirror. The Business Case already expects "a push to `origin` appears on GitHub" (success criterion 3 of [BC-001]).
- Checklist used: [QC-MIL-001] for [MIL-008]. The other artifacts were changed, not created; their change is checked below.
- Review date: 2026-10-08
## Checklist Results (QC-MIL-001)
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | A concrete deliverable is defined for every gate | Pass | The script that adds `origin` only, the documents that agree with it, the README and the tests. |
| 2 | Explicit Go/No-Go criteria are stated for each gate | Pass | Seven criteria, each with an objective Go and No-Go: the remote list with and without GitHub, the contents of `.git/config`, an existing `github` remote kept, the other steps unchanged, the documents in agreement, the acceptance criteria of US-001.03, and the version printed by `--version`. |
| 3 | Dependencies on other milestones are explicitly mapped | Pass | [MIL-002] (the mirror makes the second remote unnecessary) and [MIL-003] (the step that changes), each with its reason. |
| 4 | Each milestone is traceable to a Business Case objective or KPI | Pass | Objective 4 and success criteria 1 and 3 of [BC-001], and US-001.03. |
| 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-12-18, proposed in [PP-001]; the Business Case sets no deadline. |
## Change checks on the other artifacts
| Artifact | Change | Status | Evidence/Notes |
| --- | --- | --- | --- |
| [BC-001] | Objective 4 names `origin` as the only remote | Pass | Success criterion 3 already says a push to `origin` appears on GitHub. |
| [US-001] | US-001.03 acceptance criterion; [MIL-008] added to the CrossReference | Pass | One given/when/then; it says there is no `github` remote and why. |
| [UC-001] | Postcondition, step 8 and the rule for step 8; the rule for steps 3, 5 and 7 no longer names the remote | Pass | The rule says an existing remote of another name is never removed or replaced. |
| [OC-001] | P9 | Pass | Numbering is kept: P9 now says no other remote was associated, so the references to P10 to P14 in [SD-001] and [DCD-002] still hold. |
| [SD-001] | `build(directory, giteaRepository, sshPassed)`, the returned `localProject (remote origin)` and the P9 row | Pass | The GitHub repository is no longer passed to `LocalProjectBuilder`. |
| [DCD-001], [DCD-002] | `build` loses its `target` parameter; `Remote` lists `origin` only; a Local Project has 1 Remote; the `build` mapping row | Pass | Names match [SD-001]; the class table and the mapping table agree with the diagram. |
| [DM-001], [DM-002] | A Local Project has 1 Remote (was 1..2); the Remote description | Pass | Names unchanged. The dictionary needs no change: the term Remote and its definition do not name `github`. |
| [MIL-003] | Criterion 1 and task 1 | Pass | They now say `origin` only and point to [MIL-008]. |
| [PP-001] | Phase [MIL-008], its window, the Gantt, the scope coverage and the dependency chain | Pass | Proposed dates 2026-12-14 to 2026-12-18. |
| [TM-001] | Rows for [MIL-008] and this record; Last Reviewed updated | Pass | See the matrix. |
The code, the README and the tests (the three tasks of [MIL-008]) were changed after S01 waived the plan in chat for this request, so the milestone was not synced as issues first. `create_local_project` adds `origin` only, `github_remote_url` is gone, the README describes the one remote, and `tests/test-local.sh` checks the remote list with and without GitHub, no GitHub address in `.git/config`, and a `github` remote from an earlier version kept. The full suite passed with 1141 checks and no failed static check. Criterion 5 of [MIL-008] is still for S02 to check against the documents.
## Overall Verdict
Go — the documents, the code, the README and the tests agree with each other, and every criterion of the checklist passes. Drafted by Claude Code for S02; the author and reviewer are the same person for now. S02 gave the Go in chat on 2026-10-08 for [MIL-008] and the changes it causes, and the Version History rows of the changed documents were set to `Accepted` and the rows before them to `Deprecated`.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| None | - | - |
---
[MIL-008]: ../../milestones/mil-008-gitea-only-remote.md
[MIL-002]: ../../milestones/mil-002-repositories-and-mirror.md
[MIL-003]: ../../milestones/mil-003-scaffold-and-release.md
[QC-MIL-001]: ../../../framework/qc/qc-milestones-gateways.md
[BC-001]: ../../business-case.md
[US-001]: ../../user-stories.md
[UC-001]: ../../uc-001/uc.md
[OC-001]: ../../uc-001/oc.md
[SD-001]: ../../uc-001/sd.md
[DCD-001]: ../../uc-001/dcd.md
[DCD-002]: ../../dcd.md
[DM-001]: ../../uc-001/dm.md
[DM-002]: ../../domain-model.md
[PP-001]: ../../project-plan.md
[TM-001]: ../traceability-matrix.md
[039a28c]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/039a28c01b56f8cf0af73f55d1a604b43d67ba03
[1056639]: https://git.tirsystem.com/TirSystem-BashScript/RepoFoundry/commit/1056639d5b0f84ce8b591e33e8f5a1e03e99de37
@@ -0,0 +1,88 @@
# SQA Review Record: Framework files excluded from git
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-032 |
| CrossReference | [MIL-009], [QC-MIL-001], [MIL-003], [MIL-005], [BC-001], [US-001], [UC-001], [OC-001], [SD-001], [DCD-001], [DCD-002], [DM-001], [DM-002], [DICT-001], [PP-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-08 | Accepted | Jens Tirsvad Nielsen | S02 | Initial version: draft for S02's decision | [1b1b6be] |
---
## Artifact Under Review
- Instance reviewed: [MIL-009], and the changes it causes in [BC-001], [US-001], [UC-001], [OC-001], [SD-001], [DCD-001], [DCD-002], [DM-001], [DM-002], [DICT-001], [PP-001] and [TM-001].
- Why: a new project gets `.claude` and `.agents` (the framework's skills, made by `install-skills.sh`) and `AGENTS.md` (copied from a framework template). The request is that git excludes these three in the new project after the framework is installed. The phase adds that step, using the `.git/info/exclude` mechanism that [MIL-005] already uses for the project's `.env`.
- Checklist used: [QC-MIL-001] for [MIL-009]. The other artifacts were changed, not created; their change is checked below.
- Review date: 2026-10-08
## Checklist Results (QC-MIL-001)
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | A concrete deliverable is defined for every gate | Pass | The script with a new "Git excludes" step that writes `/.claude`, `/.agents` and `/AGENTS.md` to `.git/info/exclude`, shown in the dry-run plan and the summary; the documents that agree with it; the README; the tests; version 0.3.2 and release `v0.3.2`. The cases that are not excluded (framework steps skipped, a tracked path, `framework`, `.gitmodules`, the registry) are stated. |
| 2 | Explicit Go/No-Go criteria are stated for each gate | Pass | Eleven criteria, each with an objective Go and No-Go: ignored and absent from `git status`, only `.git/info/exclude` written, a second run and a file without a final newline, a tracked path, framework steps skipped, paths that must stay visible, dry run and summary, the `.env` exclusion unchanged, the documents in agreement, the acceptance criteria of US-001.03, and the version. Criteria 9 and 10 are "Reviewed by S02" and "Verified", as in [MIL-007] and [MIL-008]. |
| 3 | Dependencies on other milestones are explicitly mapped | Pass | [MIL-003] (the framework, skills and templates steps install the files) and [MIL-005] (the `.git/info/exclude` code is reused), each with its reason. |
| 4 | Each milestone is traceable to a Business Case objective or KPI | Pass | Objective 5 of [BC-001], which this phase extends, and US-001.03. No success criterion fits, so none is cited. |
| 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-12-23, proposed in [PP-001]; the window 2026-12-21 (Monday) to 2026-12-23 (Wednesday) follows [MIL-008], which ends Friday 2026-12-18. The Business Case sets no deadline. |
## Change checks on the other artifacts
| Artifact | Change | Status | Evidence/Notes |
| --- | --- | --- | --- |
| [BC-001] | Objective 5 and one in-scope item name the exclusion | Pass | The out-of-scope list (no first commit) is consistent: nothing is committed. |
| [US-001] | US-001.03 acceptance criterion; [MIL-009] in the CrossReference and the Traces row | Pass | One given/when/then. No new story: the behavior belongs to the local-project story, so the count of seven stories is unchanged. |
| [UC-001] | Postcondition, step 9, extension 9f, and a rule for step 9 | Pass | The rule fixes the mechanism (`.git/info/exclude`, never `.gitignore`), the whole folders, the paths that stay visible, and a tracked path. |
| [OC-001] | P15 and two exceptions | Pass | P15 is appended, so P1 to P14 and the references to them in [SD-001], [DCD-001] and [DCD-002] still hold. |
| [SD-001] | Self-message `excludeFromGit(localProject)` in `install`, return `installResult (trackedPaths)`, coverage row P15 | Pass | The pattern is the one already used for `requestSync`. Diagram not rendered (see action items). |
| [DCD-001], [DCD-002] | `FrameworkInstaller.excludeFromGit`, `InstallResult.trackedPaths`, the class table, the method traceability and the DTO sentence | Pass | Names and signature match [SD-001]. Diagram not rendered (see action items). |
| [DM-001], [DM-002], [DICT-001] | Definitions of Framework Setup and Template | Pass | No new concept or attribute, as for Credentials File ("ignored by git"). The use-case model, the project model and the dictionary say the same. |
| [PP-001] | Phase [MIL-009], its window, the Gantt, the scope coverage, the dependency chain, "nine phases" and the end date | Pass | Proposed dates 2026-12-21 to 2026-12-23. |
| [TM-001] | Row for [MIL-009] and this record; Last Reviewed updated for each changed artifact | Pass | See the matrix. |
Mechanical checks run for this review: every link definition of the changed files points at an existing file and none is unused; the Gantt dates fall on the weekdays named above; [MIL-009] has 11 criteria and 5 tasks; `sync-project.sh --milestone MIL-009` parses it (milestone 87, issues #65 to #69 created on Gitea); the new names (`excludeFromGit`, `trackedPaths`, P15) are the same in every document that uses them.
Not covered by this review: the code, the README and the tests (tasks 2 to 5 of [MIL-009]) do not exist yet. Criteria 1 to 8, 10 and 11 of [MIL-009] are checked at its gate, not here; criterion 9 is checked then too.
## Consequences S02 accepts with a Go
- The whole folders `.claude` and `.agents` are excluded, as requested, not only their `skills` folders. Anything else a project keeps there (settings, agents) is not tracked either.
- The entries live in `.git/info/exclude`, which belongs to the clone. A fresh clone has neither the entries nor the files. The skills can be made again with `install-skills.sh`; `AGENTS.md` cannot, because it is a copy of a template that the project edits.
## Overall Verdict
Go — every criterion of the checklist passes and the changed documents agree with each other. One check was not done: the PlantUML diagrams of [SD-001], [DCD-001] and [DCD-002] were not rendered, because no PlantUML server is configured and the framework does not choose one. Drafted by Claude Code for S02; the author and reviewer are the same person for now, as in [RC-031]. S02 gave the Go in chat on 2026-10-08, accepted the consequences listed above, and waived the diagram check; the Version History rows of [MIL-009] and the changed documents were set to `Accepted` and the rows before them to `Deprecated`.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| None. The diagram check was waived by S02 in chat on 2026-10-08; the three diagrams stay unrendered, and a syntax error found later is fixed in the document concerned | - | - |
---
[MIL-009]: ../../milestones/mil-009-exclude-framework-files.md
[QC-MIL-001]: ../../../framework/qc/qc-milestones-gateways.md
[MIL-003]: ../../milestones/mil-003-scaffold-and-release.md
[MIL-005]: ../../milestones/mil-005-credentials.md
[MIL-007]: ../../milestones/mil-007-framework-checklists.md
[MIL-008]: ../../milestones/mil-008-gitea-only-remote.md
[BC-001]: ../../business-case.md
[US-001]: ../../user-stories.md
[UC-001]: ../../uc-001/uc.md
[OC-001]: ../../uc-001/oc.md
[SD-001]: ../../uc-001/sd.md
[DCD-001]: ../../uc-001/dcd.md
[DCD-002]: ../../dcd.md
[DM-001]: ../../uc-001/dm.md
[DM-002]: ../../domain-model.md
[DICT-001]: ../../dictionary.md
[PP-001]: ../../project-plan.md
[TM-001]: ../traceability-matrix.md
[RC-031]: ./rc-031-gitea-only-remote.md
[1b1b6be]: https://git.tirsystem.com/TirSystem-BashScript/RepoFoundry/commit/1b1b6bec2effe5f566479ef94fcc5bc73a9fd609
+22 -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-07 | Deprecated | Jens Tirsvad Nielsen | S02 | Added MIL-007, UC-002, SSD-002, DM-003, OC-002, SD-002 and DCD-003 with their reviews RC-022 to RC-028 | [1cd27f7] | | 2026-10-08 | Deprecated | Jens Tirsvad Nielsen | S02 | Added MIL-009 (framework files excluded from git); not yet reviewed | [08cb484] |
| 2026-10-07 | Accepted | Jens Tirsvad Nielsen | S02 | Added review RC-030 (.env optional) | [24f1507] | | 2026-10-08 | Accepted | Jens Tirsvad Nielsen | S02 | Added review RC-032 (MIL-009: framework files excluded from git), Go | [1b1b6be] |
--- ---
@@ -24,19 +24,21 @@ 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], [MIL-005], [MIL-006], [MIL-007], [US-001], [UCD-001] | [RC-010], [RC-018], [RC-020], [RC-022], [RC-029] | | [BC-001] | BC | - | [SA-001], [PP-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007], [MIL-008], [MIL-009], [US-001], [UCD-001] | [RC-010], [RC-018], [RC-020], [RC-022], [RC-029], [RC-031], [RC-032] |
| [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], [MIL-005], [MIL-006], [MIL-007] | [RC-012], [RC-018], [RC-020], [RC-022] | | [PP-001] | PP | [BC-001], [SA-001] | [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007], [MIL-008], [MIL-009] | [RC-012], [RC-018], [RC-020], [RC-022], [RC-031], [RC-032] |
| [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], [RC-031] |
| [MIL-004] | MIL | [BC-001], [PP-001] | [US-001] | [RC-018], [RC-019] | | [MIL-004] | MIL | [BC-001], [PP-001] | [US-001] | [RC-018], [RC-019] |
| [MIL-005] | MIL | [BC-001], [PP-001] | [US-001] | [RC-020] | | [MIL-005] | MIL | [BC-001], [PP-001] | [US-001] | [RC-020] |
| [MIL-006] | MIL | [BC-001], [PP-001] | [US-001] | [RC-022] | | [MIL-006] | MIL | [BC-001], [PP-001] | [US-001] | [RC-022] |
| [MIL-007] | MIL | [BC-001], [PP-001] | [US-001], [UC-002] | [RC-022], [RC-029], [RC-030] | | [MIL-007] | MIL | [BC-001], [PP-001] | [US-001], [UC-002] | [RC-022], [RC-029], [RC-030] |
| [MIL-008] | MIL | [BC-001], [PP-001] | [US-001] | [RC-031] |
| [MIL-009] | MIL | [BC-001], [PP-001] | [US-001] | [RC-032] |
| [UCD-001] | UCD | [BC-001], [SA-001] | [US-001], [UC-001], [UC-002] | [RC-009], [RC-023] | | [UCD-001] | UCD | [BC-001], [SA-001] | [US-001], [UC-001], [UC-002] | [RC-009], [RC-023] |
| [US-001] | US | [BC-001], [UCD-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007] | [UC-001] | [RC-001], [RC-020], [RC-022], [RC-029], [RC-030] | | [US-001] | US | [BC-001], [UCD-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007], [MIL-008], [MIL-009] | [UC-001] | [RC-001], [RC-020], [RC-022], [RC-029], [RC-030], [RC-031], [RC-032] |
| [UC-001] | UC | [UCD-001], [US-001], [SA-001] | [SSD-001], [DM-001] | [RC-002], [RC-020], [RC-023] | | [UC-001] | UC | [UCD-001], [US-001], [SA-001] | [SSD-001], [DM-001] | [RC-002], [RC-020], [RC-023], [RC-031], [RC-032] |
| [UC-002] | UC | [UCD-001], [US-001], [SA-001], [BC-001] | [SSD-002], [DM-003] | [RC-023], [RC-029], [RC-030] | | [UC-002] | UC | [UCD-001], [US-001], [SA-001], [BC-001] | [SSD-002], [DM-003] | [RC-023], [RC-029], [RC-030] |
| [SSD-002] | SSD | [UC-002], [DM-003] | [OC-002] | [RC-024], [RC-029] | | [SSD-002] | SSD | [UC-002], [DM-003] | [OC-002] | [RC-024], [RC-029] |
| [DM-003] | DM | [UC-002], [UCD-001], [SSD-002], [DICT-001], [DM-001] | [OC-002], [DCD-003] | [RC-025], [RC-029] | | [DM-003] | DM | [UC-002], [UCD-001], [SSD-002], [DICT-001], [DM-001] | [OC-002], [DCD-003] | [RC-025], [RC-029] |
@@ -44,13 +46,13 @@ updated whenever an artifact instance is created or reviewed.
| [SD-002] | SD | [OC-002], [DCD-003] | [DCD-003] | [RC-027], [RC-029] | | [SD-002] | SD | [OC-002], [DCD-003] | [DCD-003] | [RC-027], [RC-029] |
| [DCD-003] | DCD | [DM-003], [SD-002], [DICT-001], [UC-002], [DCD-001] | [DCD-002] | [RC-028], [RC-029], [RC-030] | | [DCD-003] | DCD | [DM-003], [SD-002], [DICT-001], [UC-002], [DCD-001] | [DCD-002] | [RC-028], [RC-029], [RC-030] |
| [SSD-001] | SSD | [UC-001] | [OC-001] | [RC-003], [RC-020] | | [SSD-001] | SSD | [UC-001] | [OC-001] | [RC-003], [RC-020] |
| [DM-001] | DM | [UC-001], [SSD-001] | [DM-002], [DICT-001], [OC-001], [DCD-001] | [RC-004], [RC-020] | | [DM-001] | DM | [UC-001], [SSD-001] | [DM-002], [DICT-001], [OC-001], [DCD-001] | [RC-004], [RC-020], [RC-031], [RC-032] |
| [DM-002] | DM | [DM-001] | [DICT-001], [DCD-001], [DCD-002] | [RC-005], [RC-020], [RC-025], [RC-029] | | [DM-002] | DM | [DM-001] | [DICT-001], [DCD-001], [DCD-002] | [RC-005], [RC-020], [RC-025], [RC-029], [RC-031], [RC-032] |
| [DICT-001] | DICT | [BC-001], [SA-001], [DM-001], [DM-002] | [OC-001], [SD-001] | [RC-008], [RC-020], [RC-025], [RC-029] | | [DICT-001] | DICT | [BC-001], [SA-001], [DM-001], [DM-002] | [OC-001], [SD-001] | [RC-008], [RC-020], [RC-025], [RC-029], [RC-032] |
| [OC-001] | OC | [SSD-001], [DM-001] | [SD-001] | [RC-006], [RC-020], [RC-026] | | [OC-001] | OC | [SSD-001], [DM-001] | [SD-001] | [RC-006], [RC-020], [RC-026], [RC-031], [RC-032] |
| [SD-001] | SD | [OC-001] | [DCD-001] | [RC-007], [RC-020], [RC-021] | | [SD-001] | SD | [OC-001] | [DCD-001] | [RC-007], [RC-020], [RC-021], [RC-031], [RC-032] |
| [DCD-001] | DCD | [UC-001], [DM-001], [DM-002], [OC-001], [SD-001], [DICT-001] | [DCD-002] | [RC-021] | | [DCD-001] | DCD | [UC-001], [DM-001], [DM-002], [OC-001], [SD-001], [DICT-001] | [DCD-002] | [RC-021], [RC-031], [RC-032] |
| [DCD-002] | DCD | [DCD-001], [DCD-003], [DM-002], [DICT-001] | - | [RC-021], [RC-028], [RC-029], [RC-030] | | [DCD-002] | DCD | [DCD-001], [DCD-003], [DM-002], [DICT-001] | - | [RC-021], [RC-028], [RC-029], [RC-030], [RC-031], [RC-032] |
## Coverage Notes ## Coverage Notes
@@ -69,6 +71,8 @@ updated whenever an artifact instance is created or reviewed.
[MIL-005]: ../milestones/mil-005-credentials.md [MIL-005]: ../milestones/mil-005-credentials.md
[MIL-006]: ../milestones/mil-006-project-license.md [MIL-006]: ../milestones/mil-006-project-license.md
[MIL-007]: ../milestones/mil-007-framework-checklists.md [MIL-007]: ../milestones/mil-007-framework-checklists.md
[MIL-008]: ../milestones/mil-008-gitea-only-remote.md
[MIL-009]: ../milestones/mil-009-exclude-framework-files.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-019]: ./reviews/rc-019-mil-004-code.md
[RC-020]: ./reviews/rc-020-mil-005.md [RC-020]: ./reviews/rc-020-mil-005.md
@@ -82,6 +86,8 @@ updated whenever an artifact instance is created or reviewed.
[RC-028]: ./reviews/rc-028-dcd-003.md [RC-028]: ./reviews/rc-028-dcd-003.md
[RC-029]: ./reviews/rc-029-default-config-files.md [RC-029]: ./reviews/rc-029-default-config-files.md
[RC-030]: ./reviews/rc-030-env-optional.md [RC-030]: ./reviews/rc-030-env-optional.md
[RC-031]: ./reviews/rc-031-gitea-only-remote.md
[RC-032]: ./reviews/rc-032-exclude-framework-files.md
[DCD-001]: ../uc-001/dcd.md [DCD-001]: ../uc-001/dcd.md
[DCD-002]: ../dcd.md [DCD-002]: ../dcd.md
[UCD-001]: ../use-case-diagram.md [UCD-001]: ../use-case-diagram.md
@@ -116,5 +122,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
[1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f [08cb484]: https://git.tirsystem.com/TirSystem-BashScript/RepoFoundry/commit/08cb484498bab3d9480decda9df89e9564438185
[24f1507]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/24f15070fc73fb06e61865141fe0b825ea9e821e [1b1b6be]: https://git.tirsystem.com/TirSystem-BashScript/RepoFoundry/commit/1b1b6bec2effe5f566479ef94fcc5bc73a9fd609
+16 -12
View File
@@ -9,8 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-06 | Deprecated | Jens Tirsvad Nielsen | S02 | ProjectRequest carries the license that applies | [d773fa9] | | 2026-10-08 | Deprecated | Jens Tirsvad Nielsen | S02 | LocalProjectBuilder.build() no longer takes the GitHub repository; a Local Project has one Remote, origin (MIL-008) | [039a28c] |
| 2026-10-07 | Accepted | Jens Tirsvad Nielsen | S02 | Note that DCD-003 and DCD-002 supersede the signature of startProjectCreation | [0b0a3b4] | | 2026-10-08 | Accepted | Jens Tirsvad Nielsen | S02 | FrameworkInstaller.excludeFromGit() and InstallResult.trackedPaths: `.claude`, `.agents` and `AGENTS.md` are excluded from git; P15 (MIL-009) | [08cb484] |
--- ---
@@ -75,10 +75,11 @@ class GitHubClient <<facade>> {
+createEmptyRepository(request : ProjectRequest) : GitHubRepository +createEmptyRepository(request : ProjectRequest) : GitHubRepository
} }
class LocalProjectBuilder { class LocalProjectBuilder {
+build(directory : Path, source : GiteaRepository, target : GitHubRepository [0..1], sshPassed : Boolean) : LocalProject +build(directory : Path, source : GiteaRepository, sshPassed : Boolean) : LocalProject
} }
class FrameworkInstaller { class FrameworkInstaller {
+install(project : LocalProject, enablePlanGate : Boolean) : InstallResult +install(project : LocalProject, enablePlanGate : Boolean) : InstallResult
-excludeFromGit(project : LocalProject) : String [0..*]
} }
class SummaryReport { class SummaryReport {
+compose(request : ProjectRequest) : Summary +compose(request : ProjectRequest) : Summary
@@ -167,7 +168,9 @@ class Template {
-name : String -name : String
-isCopied : Boolean -isCopied : Boolean
} }
class InstallResult <<dto>> class InstallResult <<dto>> {
-trackedPaths : String [0..*]
}
class Summary { class Summary {
-createdItems : String [0..*] -createdItems : String [0..*]
-skippedItems : String [0..*] -skippedItems : String [0..*]
@@ -215,7 +218,7 @@ PushMirror "0..*" --> "1" GiteaRepository : source
PushMirror "0..*" --> "1" GitHubRepository : target PushMirror "0..*" --> "1" GitHubRepository : target
PushMirror "0..*" --> "1" Credential : authorised by PushMirror "0..*" --> "1" Credential : authorised by
LocalProject "1" *-- "1..2" Remote LocalProject "1" *-- "1" Remote
Remote "0..*" --> "1" Repository : points to Remote "0..*" --> "1" Repository : points to
LocalProject "1" *-- "1" Submodule LocalProject "1" *-- "1" Submodule
LocalProject "1" *-- "1" HookSetup LocalProject "1" *-- "1" HookSetup
@@ -245,7 +248,7 @@ Repository "0..*" --> "1" Visibility
| `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` | | `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` | | `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` | | `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` | | `FrameworkInstaller` | Framework, Framework Setup, Template | Adds the framework submodule, installs skills and hooks once, copies the templates without overwriting, and excludes `.claude`, `.agents` and `AGENTS.md` from git through `.git/info/exclude`. | none | `install`, `excludeFromGit` |
| `SummaryReport` | Summary | Composes the report of what was created, skipped or failed. | none | `compose` | | `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 | | `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 | | `Configuration` | Configuration | Holds the service addresses, the credentials and any preset project details. | `giteaUrl`, `giteaApiUrl`, `githubWebUrl`, `githubApiUrl`, `giteaSshPort`, `mirrorInterval`, `frameworkRepo`, `presetDetails` | none |
@@ -261,12 +264,12 @@ Repository "0..*" --> "1" Visibility
| `LicenseFile` | License | The license file in the Gitea repository when a license applies. | `key` | none | | `LicenseFile` | License | The license file in the Gitea repository when a license applies. | `key` | none |
| `PushMirror` | Mirror | The Gitea to GitHub push mirror. | `interval`, `syncOnCommit` | 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 | | `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 | | `Remote` | Remote | A named link to a repository (`origin`), without a credential. | `name`, `address` | none |
| `Submodule` | Framework | The framework added to the local project. | `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 | | `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 | | `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 | | `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 | | `InstallResult` | none (carries the result of one operation) | Returns the submodule, the hook setup and the templates of `install`, and the paths git already tracks that could not be excluded. | `trackedPaths` | none |
| `Summary` | Summary | The report returned to the Maintainer; it contains no credential. | `createdItems`, `skippedItems`, `nextSteps` | 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 | | `Visibility` | none (enumeration of a Project and Repository attribute) | The two allowed visibilities. | `private`, `public` | none |
@@ -291,8 +294,9 @@ Repository "0..*" --> "1" Visibility
| `GiteaClient.requestSync(mirror) : void` | [SD-001] `requestSync(pushMirror)`; P6 | | `GiteaClient.requestSync(mirror) : void` | [SD-001] `requestSync(pushMirror)`; P6 |
| `GitHubClient(configuration)` | [SD-001] `new(configuration)` to `GitHubClient` | | `GitHubClient(configuration)` | [SD-001] `new(configuration)` to `GitHubClient` |
| `GitHubClient.createEmptyRepository(request) : GitHubRepository` | [SD-001] `createEmptyRepository(request)`; P5 | | `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 | | `LocalProjectBuilder.build(directory, source, sshPassed) : LocalProject` | [SD-001] `build(directory, giteaRepository, sshPassed)`; P7, P8, P9 |
| `FrameworkInstaller.install(project, enablePlanGate) : InstallResult` | [SD-001] `install(localProject, enablePlanGate)`; P10, P11, P12 | | `FrameworkInstaller.install(project, enablePlanGate) : InstallResult` | [SD-001] `install(localProject, enablePlanGate)`; P10, P11, P12 |
| `FrameworkInstaller.excludeFromGit(project) : String [0..*]` | [SD-001] `excludeFromGit(localProject)`; P15 |
| `SummaryReport.compose(request) : Summary` | [SD-001] `compose(projectRequest)`; P13 | | `SummaryReport.compose(request) : Summary` | [SD-001] `compose(projectRequest)`; P13 |
## Pattern Annotations ## Pattern Annotations
@@ -304,7 +308,7 @@ Repository "0..*" --> "1" Visibility
| Pure Fabrication (GRASP) | `ConfigLoader`, `ToolChecker`, `CredentialCollector`, `EnvFileWriter`, `Preflight`, `LocalProjectBuilder`, `FrameworkInstaller`, `SummaryReport` | No domain concept owns these responsibilities; small units keep cohesion high | | 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 | | 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 | | 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 | | Data Transfer Object (GoF-style) | `InstallResult` | Carries the results of `install` (the submodule, the hook setup, the templates and the tracked paths) in one return value |
## Dependency Check ## Dependency Check
@@ -339,7 +343,7 @@ SOLID check: no class has more than one reason to change (one host API, one kind
[MIL-005]: ../milestones/mil-005-credentials.md [MIL-005]: ../milestones/mil-005-credentials.md
[DICT-001]: ../dictionary.md [DICT-001]: ../dictionary.md
[DCD-002]: ../dcd.md [DCD-002]: ../dcd.md
[d773fa9]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/d773fa91df5a54090254e12e074880fb6526a9ff
[DCD-003]: ../uc-002/dcd.md [DCD-003]: ../uc-002/dcd.md
[UC-002]: ../uc-002/uc.md [UC-002]: ../uc-002/uc.md
[0b0a3b4]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/0b0a3b419a1157b23bddd2f8957a08adaf6974a6 [039a28c]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/039a28c01b56f8cf0af73f55d1a604b43d67ba03
[08cb484]: https://git.tirsystem.com/TirSystem-BashScript/RepoFoundry/commit/08cb484498bab3d9480decda9df89e9564438185
+9 -9
View File
@@ -9,8 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-06 | Proposed | Jens Tirsvad Nielsen | S02 | License applies when configured, not only when GitHub is chosen | [d773fa9] | | 2026-10-08 | Deprecated | Jens Tirsvad Nielsen | S02 | A Local Project has one Remote, origin, no longer one or two (MIL-008) | [039a28c] |
| 2026-10-07 | Proposed | Jens Tirsvad Nielsen | S02 | License: the AGPL-3.0 default needs GitHub and a public project | [1cd27f7] | | 2026-10-08 | Accepted | Jens Tirsvad Nielsen | S02 | Framework Setup and Template: the folders that hold the skills and the copy of AGENTS.md are ignored by git (MIL-009) | [08cb484] |
--- ---
@@ -104,7 +104,7 @@ Mirror "1" --> "1" GiteaRepository : copies from
Mirror "1" --> "1" GitHubRepository : copies to Mirror "1" --> "1" GitHubRepository : copies to
Mirror "0..*" --> "1" AccessToken : is authorised by Mirror "0..*" --> "1" AccessToken : is authorised by
LocalProject "1" --> "1" Project : is the working copy of LocalProject "1" --> "1" Project : is the working copy of
LocalProject "1" --> "1..2" Remote : has LocalProject "1" --> "1" Remote : has
Remote "0..*" --> "1" Repository : points to Remote "0..*" --> "1" Repository : points to
LocalProject "1" --> "1" Framework : includes LocalProject "1" --> "1" Framework : includes
LocalProject "1" --> "1" FrameworkSetup : has LocalProject "1" --> "1" FrameworkSetup : has
@@ -133,10 +133,10 @@ Summary "1" --> "1" Project : reports on
| License | The legal terms file added to a Gitea Repository when a license applies: the one set in the Configuration, or AGPL-3.0 when GitHub is chosen, the project is public and none is set | name | [UC-001] step 6 "license" | | License | The legal terms file added to a Gitea Repository when a license applies: the one set in the Configuration, or AGPL-3.0 when GitHub is chosen, the project is public and none is set | name | [UC-001] step 6 "license" |
| Mirror | The push mirror that copies a Gitea Repository to a GitHub Repository | interval, sync on commit | [UC-001] step 7 "push mirror" | | Mirror | The push mirror that copies a Gitea Repository to a GitHub Repository | interval, sync on commit | [UC-001] step 7 "push mirror" |
| Local Project | The project directory on the Maintainer's machine | directory | [UC-001] step 8 "local project" | | Local Project | The project directory on the Maintainer's machine | directory | [UC-001] step 8 "local project" |
| Remote | A named link from a Local Project to a Repository (`origin`, `github`) | name, address | [UC-001] step 8 "remote" | | Remote | A named link from a Local Project to a Repository (`origin`) | name, address | [UC-001] step 8 "remote" |
| 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; the folders that hold the skills (`.claude`, `.agents`) are ignored by git | plan gate enabled | [UC-001] step 9 "skills and hooks", "plan gate", "excludes from git" |
| 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); the copy of `AGENTS.md` is ignored by git | name | [UC-001] step 9 "templates", "excludes from git" |
| 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" | | 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" |
@@ -157,7 +157,7 @@ Summary "1" --> "1" Project : reports on
| Mirror | copies to | GitHub Repository | 1 to 1 | | Mirror | copies to | GitHub Repository | 1 to 1 |
| Mirror | is authorised by | Access Token | 0..* to 1 | | Mirror | is authorised by | Access Token | 0..* to 1 |
| Local Project | is the working copy of | Project | 1 to 1 | | Local Project | is the working copy of | Project | 1 to 1 |
| Local Project | has | Remote | 1 to 1..2 | | Local Project | has | Remote | 1 to 1 |
| Remote | points to | Repository | 0..* to 1 | | Remote | points to | Repository | 0..* to 1 |
| Local Project | includes | Framework | 1 to 1 | | Local Project | includes | Framework | 1 to 1 |
| Local Project | has | Framework Setup | 1 to 1 | | Local Project | has | Framework Setup | 1 to 1 |
@@ -180,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
[d773fa9]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/d773fa91df5a54090254e12e074880fb6526a9ff [039a28c]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/039a28c01b56f8cf0af73f55d1a604b43d67ba03
[1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f [08cb484]: https://git.tirsystem.com/TirSystem-BashScript/RepoFoundry/commit/08cb484498bab3d9480decda9df89e9564438185
+8 -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-07 | Deprecated | Jens Tirsvad Nielsen | S02 | P4: the AGPL-3.0 default needs githubOwner and a public visibility; P10: the framework's own submodules were initialised, with an exception for a failed fetch | [1cd27f7] | | 2026-10-08 | Deprecated | Jens Tirsvad Nielsen | S02 | P9: no other remote is associated with the local project (MIL-008) | [039a28c] |
| 2026-10-07 | Accepted | Jens Tirsvad Nielsen | S02 | Note that OC-002 and DCD-002 supersede the signature of startProjectCreation | [0b0a3b4] | | 2026-10-08 | Accepted | Jens Tirsvad Nielsen | S02 | P15 and two exceptions: `.claude`, `.agents` and `AGENTS.md` are excluded from git; a tracked path is reported (MIL-009) | [08cb484] |
--- ---
@@ -71,12 +71,13 @@ Concepts below use the IT terms of [DICT-001] for the PO concepts of [DM-001]. `
- P6. If `githubOwner` is present, a `PushMirror` instance was created, associated with the `GiteaRepository` as source and the `GitHubRepository` as target, with its effective sync setting recorded, and a first sync was requested. - P6. If `githubOwner` is present, a `PushMirror` instance was created, associated with the `GiteaRepository` as source and the `GitHubRepository` as target, with its effective sync setting recorded, and a first sync was requested.
- P7. A `LocalProject` instance was created at `directory`, associated with the `ProjectRequest`. If the `GiteaRepository` is not empty, the `LocalProject` holds its history, including the `LicenseFile` commit. - P7. A `LocalProject` instance was created at `directory`, associated with the `ProjectRequest`. If the `GiteaRepository` is not empty, the `LocalProject` holds its history, including the `LicenseFile` commit.
- P8. A `Remote` named `origin` was associated with the `LocalProject`, pointing at the `GiteaRepository` over SSH if the SSH test passed, otherwise over HTTPS, with no credential in its URL. - P8. A `Remote` named `origin` was associated with the `LocalProject`, pointing at the `GiteaRepository` over SSH if the SSH test passed, otherwise over HTTPS, with no credential in its URL.
- P9. If `githubOwner` is present, a `Remote` named `github` was associated with the `LocalProject`, pointing at the `GitHubRepository`, with no credential in its URL. - P9. No other `Remote` was associated with the `LocalProject`, whether or not `githubOwner` is present: the `GitHubRepository`, when there is one, is reached through the `PushMirror` of P6, not through a remote.
- P10. A `Submodule` named `framework` was associated with the `LocalProject`, and the submodules the framework itself holds (`qc`) were initialised. - P10. A `Submodule` named `framework` was associated with the `LocalProject`, and the submodules the framework itself holds (`qc`) were initialised.
- 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. - 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.
- P15. If the `Submodule` and the `HookSetup` were created (P10, P11), the paths `.claude`, `.agents` and `AGENTS.md` of the `LocalProject` are excluded from git: each has an entry in the `.git/info/exclude` of the `LocalProject`, whether or not the path exists yet and whether or not the Maintainer declined replacing `AGENTS.md` (P12). No tracked file was changed and no commit was made. A path that git already tracks stays tracked, and the `Summary` lists it as not excluded. `framework`, `.gitmodules` and `docs/artifact-registry.md` are not excluded.
**Exceptions** **Exceptions**
@@ -91,6 +92,8 @@ Concepts below use the IT terms of [DICT-001] for the PO concepts of [DM-001]. `
| 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` |
| The framework's own submodules cannot be fetched (P10) | The `Summary` lists the `framework` `Submodule` as added, its own submodules as failed, and the command `git submodule update --init --recursive` to run by hand | | The framework's own submodules cannot be fetched (P10) | The `Summary` lists the `framework` `Submodule` as added, its own submodules as failed, and the command `git submodule update --init --recursive` to run by hand |
| 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 |
| SSH to port 10022 fails and the Maintainer goes on without the framework (P10, P11, P12, P15) | No `Submodule`, `HookSetup` or template is created and no path is excluded; the `Summary` lists each of these steps as skipped |
| `.claude`, `.agents` or `AGENTS.md` is already tracked by git (P15) | The path stays tracked and is listed in the `Summary` as not excluded; the other paths are excluded |
--- ---
@@ -98,8 +101,8 @@ 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
[1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f
[OC-002]: ../uc-002/oc.md [OC-002]: ../uc-002/oc.md
[DCD-002]: ../dcd.md [DCD-002]: ../dcd.md
[UC-002]: ../uc-002/uc.md [UC-002]: ../uc-002/uc.md
[0b0a3b4]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/0b0a3b419a1157b23bddd2f8957a08adaf6974a6 [039a28c]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/039a28c01b56f8cf0af73f55d1a604b43d67ba03
[08cb484]: https://git.tirsystem.com/TirSystem-BashScript/RepoFoundry/commit/08cb484498bab3d9480decda9df89e9564438185
+10 -8
View File
@@ -9,8 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-07 | Deprecated | Jens Tirsvad Nielsen | S02 | P4: the license passed is AGPL-3.0 only for GitHub with a public project | [1cd27f7] | | 2026-10-08 | Deprecated | Jens Tirsvad Nielsen | S02 | build() no longer receives the GitHub repository and returns a local project with the remote origin; P9 (MIL-008) | [039a28c] |
| 2026-10-07 | Accepted | Jens Tirsvad Nielsen | S02 | Note that SD-002 and DCD-002 supersede the signature of startProjectCreation | [0b0a3b4] | | 2026-10-08 | Accepted | Jens Tirsvad Nielsen | S02 | install() excludes the installed files from git with excludeFromGit() and returns the tracked paths; P15 (MIL-009) | [08cb484] |
--- ---
@@ -146,15 +146,16 @@ opt githubOwner present
end end
create LB create LB
PC -> LB : build(directory, giteaRepository, gitHubRepository, sshPassed) PC -> LB : build(directory, giteaRepository, sshPassed)
activate LB activate LB
LB --> PC : localProject (remotes origin, github) LB --> PC : localProject (remote origin)
deactivate LB deactivate LB
create FI create FI
PC -> FI : install(localProject, enablePlanGate) PC -> FI : install(localProject, enablePlanGate)
activate FI activate FI
FI --> PC : installResult FI -> FI : excludeFromGit(localProject)
FI --> PC : installResult (trackedPaths)
deactivate FI deactivate FI
opt writeEnvFile opt writeEnvFile
@@ -203,12 +204,13 @@ destroy SR
| P6 PushMirror and first sync | `addPushMirror(...)` and `requestSync(pushMirror)` | | 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 no other remote (GitHub is reached through the mirror) | `build(...)` adds `origin` only |
| 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(request)` 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` | | P14 EnvFile created with the needed credentials, owner-only, ignored by git, or none when declined | `write(localProject, configuration, githubOwner present)` inside `opt writeEnvFile` |
| P15 `.claude`, `.agents` and `AGENTS.md` excluded from git; a tracked path reported | `excludeFromGit(localProject)` inside `install(...)`, and `installResult (trackedPaths)` for the `Summary` |
### Responsibility Check ### Responsibility Check
@@ -218,8 +220,8 @@ destroy SR
[OC-001]: ./oc.md [OC-001]: ./oc.md
[DCD-001]: ./dcd.md [DCD-001]: ./dcd.md
[1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f
[SD-002]: ../uc-002/sd.md [SD-002]: ../uc-002/sd.md
[DCD-002]: ../dcd.md [DCD-002]: ../dcd.md
[UC-002]: ../uc-002/uc.md [UC-002]: ../uc-002/uc.md
[0b0a3b4]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/0b0a3b419a1157b23bddd2f8957a08adaf6974a6 [039a28c]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/039a28c01b56f8cf0af73f55d1a604b43d67ba03
[08cb484]: https://git.tirsystem.com/TirSystem-BashScript/RepoFoundry/commit/08cb484498bab3d9480decda9df89e9564438185
+13 -9
View File
@@ -9,8 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-06 | Deprecated | Jens Tirsvad Nielsen | S02 | The license comes from PROJECT_LICENSE (step 6, extension 4c); AGPL-3.0 is only the default when GitHub is chosen | [d773fa9] | | 2026-10-08 | Deprecated | Jens Tirsvad Nielsen | S02 | Postcondition, step 8 and its rule: origin is the only remote; no github remote (MIL-008) | [039a28c] |
| 2026-10-07 | Accepted | Jens Tirsvad Nielsen | S02 | License rule: the AGPL-3.0 default needs GitHub and a public project (step 6, rule 6); step 9 and extension 9e fetch the framework's own submodules | [1cd27f7] | | 2026-10-08 | Accepted | Jens Tirsvad Nielsen | S02 | Postcondition, step 9, extension 9f and a rule: `.claude`, `.agents` and `AGENTS.md` are excluded from git (MIL-009) | [08cb484] |
--- ---
@@ -33,7 +33,8 @@
- **Postconditions (success guarantee):** - **Postconditions (success guarantee):**
- A repository exists on Gitea under the chosen owner. It is empty, or it holds the license file that applies: the license set in `config.env`, or AGPL-3.0 when the Maintainer chose GitHub, the project is public and no license is set. - A repository exists on Gitea under the chosen owner. It is empty, or it holds the license file that applies: the license set in `config.env`, or AGPL-3.0 when the Maintainer chose GitHub, the project is public and no license is set.
- 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 license file, if any, 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 license file, if any, 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 one credential-free remote, `origin` (Gitea), the `framework` submodule, installed skills and hooks, and the copied templates.
- Git ignores `.claude`, `.agents` and `AGENTS.md` in the local project, through the project's own `.git/info/exclude`; no tracked file is changed.
- 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. - 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.
@@ -46,8 +47,8 @@
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 a license applies, the repository is created with its license file and so is not empty; otherwise it is empty. The license that applies is the one set in `config.env` (`PROJECT_LICENSE`; `none` means no license); when none is set it is AGPL-3.0 if the Maintainer chose GitHub and the project is public, and none otherwise. The license is never asked. 6. The system creates the Gitea repository. If a license applies, the repository is created with its license file and so is not empty; otherwise it is empty. The license that applies is the one set in `config.env` (`PROJECT_LICENSE`; `none` means no license); when none is set it is AGPL-3.0 if the Maintainer chose GitHub and the project is public, and none otherwise. The license is never asked.
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. It adds no `github` remote: a push to `origin` reaches GitHub through the mirror of step 7.
9. The system adds the framework submodule and fetches its own submodules (the `qc` checklists), 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. 9. The system adds the framework submodule and fetches its own submodules (the `qc` checklists), installs its skills and hooks (and the plan gate if chosen) and copies the templates. It then excludes `.claude`, `.agents` and `AGENTS.md` from git. 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)
@@ -78,6 +79,8 @@
1. The system asks before replacing it; on no, it keeps it and reports it. 1. The system asks before replacing it; on no, it keeps it and reports it.
- 9e. The framework's own submodules cannot be fetched: - 9e. The framework's own submodules cannot be fetched:
1. The system stops the step, reports what exists and names the command to run by hand, `git submodule update --init --recursive`, without showing a credential. 1. The system stops the step, reports what exists and names the command to run by hand, `git submodule update --init --recursive`, without showing a credential.
- 9f. Git already tracks one of `.claude`, `.agents` and `AGENTS.md`:
1. The system leaves it tracked, because an exclusion does not apply to a tracked file, and names the path in the summary as not ignored.
### Special Requirements / Business Rules ### Special Requirements / Business Rules
@@ -88,11 +91,12 @@
| 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 | | 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 or mirror is created and the GitHub credentials are not required |
| 6 | The license that applies is added to the Gitea repository when it is created, so that repository is not empty: `PROJECT_LICENSE` if set (a Gitea license key such as `MIT`, or `none`), otherwise AGPL-3.0 when GitHub is chosen and the project is public, otherwise none. It is independent of the GitHub choice when set, and it is never asked | | 6 | The license that applies is added to the Gitea repository when it is created, so that repository is not empty: `PROJECT_LICENSE` if set (a Gitea license key such as `MIT`, or `none`), otherwise AGPL-3.0 when GitHub is chosen and the project is public, otherwise none. It is independent of the GitHub choice when set, and it is never asked |
| 7 | The mirror direction is Gitea to GitHub; the GitHub repository stays empty and receives its content from the mirror | | 7 | The mirror direction is Gitea to GitHub; the GitHub repository stays empty and receives its content from the mirror |
| 8 | `origin` uses HTTPS derived from `GITEA_URL`, or SSH when the SSH test in step 4 passed; when the Gitea repository is not empty (GitHub chosen) the local project is created by fetching it, not by an unrelated `git init` history | | 8 | `origin` is the only remote, with or without GitHub, and a remote already in the directory, such as a `github` remote made by an earlier version, is never removed or replaced. `origin` uses HTTPS derived from `GITEA_URL`, or SSH when the SSH test in step 4 passed; when the Gitea repository is not empty (GitHub chosen) the local project is created by fetching it, not by an unrelated `git init` history |
| 8, 9 | Nothing is overwritten or deleted without consent, and no commit is made | | 8, 9 | Nothing is overwritten or deleted without consent, and no commit is made |
| 9 | `.claude`, `.agents` and `AGENTS.md` are excluded from git only when the framework steps ran, whether or not the files were newly made, through the project's `.git/info/exclude` (never `.gitignore`, so no tracked file changes and nothing is shared with a clone), once, anchored to the project root, and the whole folders `.claude` and `.agents` are excluded, not only their `skills` folders. `framework`, `.gitmodules` and `docs/artifact-registry.md` are not excluded. A path git already tracks is never untracked or changed |
### Open Issues ### Open Issues
@@ -104,5 +108,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
[d773fa9]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/d773fa91df5a54090254e12e074880fb6526a9ff [039a28c]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/039a28c01b56f8cf0af73f55d1a604b43d67ba03
[1cd27f7]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/1cd27f77ed844773a969210a11de0d8bb98ac98f [08cb484]: https://git.tirsystem.com/TirSystem-BashScript/RepoFoundry/commit/08cb484498bab3d9480decda9df89e9564438185
+10 -7
View File
@@ -4,13 +4,13 @@
| Key | Value | | Key | Value |
| --- | --- | | --- | --- |
| ID | US-001 | | ID | US-001 |
| CrossReference | [BC-001], [UCD-001], [UC-002], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007] | | CrossReference | [BC-001], [UCD-001], [UC-002], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007], [MIL-008], [MIL-009] |
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-07 | Deprecated | Jens Tirsvad Nielsen | S02 | US-002: the default configuration files are the working folder's, then the checkout's; confirmed and named | [0ab5006] | | 2026-10-08 | Deprecated | Jens Tirsvad Nielsen | S02 | US-001.03: one remote, origin; no github remote (MIL-008) | [039a28c] |
| 2026-10-07 | Accepted | Jens Tirsvad Nielsen | S02 | US-002: only config.env is required; a .env found nowhere means the token is asked | [24f1507] | | 2026-10-08 | Accepted | Jens Tirsvad Nielsen | S02 | US-001.03: `.claude`, `.agents` and `AGENTS.md` are excluded from git; MIL-009 added to CrossReference | [08cb484] |
--- ---
@@ -56,13 +56,14 @@ The epic is split into seven stories, one per milestone (US-001.01 to US-001.07)
**Acceptance Criteria** **Acceptance Criteria**
- Given the repositories exist, when the script finishes, then the project directory has an `origin` remote and, if GitHub was chosen, a `github` remote, neither containing a credential. - Given the repositories exist, when the script finishes, then the project directory has one remote, `origin` (Gitea), with or without GitHub, containing no credential; there is no `github` remote, because a push to `origin` reaches GitHub through the mirror.
- Given the project directory, when the script finishes, then the framework, its skills and git hooks (and the plan gate if chosen) and the copied templates are in place. - Given the project directory, when the script finishes, then the framework, its skills and git hooks (and the plan gate if chosen) and the copied templates are in place.
- Given the framework steps ran, when the script finishes, then git ignores `.claude`, `.agents` and `AGENTS.md` in the project through `.git/info/exclude`: no `.gitignore` or tracked file is changed, nothing is committed, and a path git already tracks stays tracked and is named in the summary.
- Given a directory or file already exists, when the script would replace it, then it asks first. - Given a directory or file already exists, when the script would replace it, then it asks first.
| Traces to | Size | INVEST exceptions | | Traces to | Size | INVEST exceptions |
| --- | --- | --- | | --- | --- | --- |
| [UC-001] steps 8 to 10, [MIL-003] | fits one phase | Independent: needs the repositories of US-001.02 | | [UC-001] steps 8 to 10, [MIL-003], [MIL-009] | fits one phase | Independent: needs the repositories of US-001.02 |
### US-001.04 — Create a new project: preset the details ### US-001.04 — Create a new project: preset the details
@@ -165,6 +166,8 @@ Valuable, Negotiable, Estimable, Small and Testable hold for each story. Indepen
[MIL-005]: ./milestones/mil-005-credentials.md [MIL-005]: ./milestones/mil-005-credentials.md
[MIL-006]: ./milestones/mil-006-project-license.md [MIL-006]: ./milestones/mil-006-project-license.md
[MIL-007]: ./milestones/mil-007-framework-checklists.md [MIL-007]: ./milestones/mil-007-framework-checklists.md
[MIL-008]: ./milestones/mil-008-gitea-only-remote.md
[MIL-009]: ./milestones/mil-009-exclude-framework-files.md
[PP-001]: ./project-plan.md [PP-001]: ./project-plan.md
[0ab5006]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/0ab50068bf9e5be82a801af9dbe5b763eeaf7f31 [039a28c]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/039a28c01b56f8cf0af73f55d1a604b43d67ba03
[24f1507]: https://git.tirsystem.com/TirSystem-BashScript/repo_foundry/commit/24f15070fc73fb06e61865141fe0b825ea9e821e [08cb484]: https://git.tirsystem.com/TirSystem-BashScript/RepoFoundry/commit/08cb484498bab3d9480decda9df89e9564438185
+236
View File
@@ -0,0 +1,236 @@
# How to create the access tokens RepoFoundry needs
RepoFoundry talks to two hosts and needs one token for each:
| Token | Key | Needed | Type |
| --- | --- | --- | --- |
| Gitea access token | `GITEA_TOKEN` | always | an access token with scopes |
| GitHub personal access token | `GITHUB_PAT` | only when you choose GitHub | a **classic** personal access token (PAT) |
This guide shows both, step by step. The red numbers in each picture match the
numbered steps next to it.
> **About the pictures.** They are drawings of the pages, not screenshots, so
> that they show no account data. Names, colors and the position of a control
> can differ a little in your version of Gitea or GitHub. The words in bold in
> the steps are the labels to look for. A new token is shown as dots in the
> pictures; on your screen you see its real value.
The full list of what each token may do is in
[Token permissions](../README.md#token-permissions) in the README. This guide
is the click-by-click version of it.
Contents:
1. [Create a Gitea access token](#1-create-a-gitea-access-token)
2. [Create a GitHub personal access token (classic)](#2-create-a-github-personal-access-token-classic)
3. [Give the tokens to RepoFoundry](#3-give-the-tokens-to-repofoundry)
4. [Keep the tokens safe](#4-keep-the-tokens-safe)
5. [If something goes wrong](#5-if-something-goes-wrong)
---
## 1. Create a Gitea access token
The examples use `https://git.tirsystem.com`. If your Gitea runs elsewhere, use
the address you set as `GITEA_URL` in `config.env`.
### Step 1. Open your settings
Sign in to Gitea. Click your **avatar** in the top-right corner (1), then
choose **Settings** (2).
![Step 1: the avatar menu in the top-right corner, with Settings highlighted](img/gitea-1-open-settings.svg)
### Step 2. Open "Applications"
In the menu on the left, click **Applications** (1).
You can also go straight there: `<your Gitea address>/user/settings/applications`.
![Step 2: the Applications entry in the left menu of the settings](img/gitea-2-applications.svg)
### Step 3. Fill in the token form
Scroll to **Generate New Token** and fill it in:
1. **Token Name:** a name you will recognise later, for example `RepoFoundry`.
2. **Repository and Organization Access:** choose **All (public, private, and
limited)**. A token that is limited to **Public only** cannot see private
repositories or organizations, so creating a private project would fail.
3. **Select permissions:** set these three to **Read and Write** and leave
every other line at **No Access**:
| Permission | Level | Why RepoFoundry needs it |
| --- | --- | --- |
| `organization` | Read and Write | look up the owner and your rights in it, create repositories in an organization |
| `repository` | Read and Write | create the repository and manage its push mirror |
| `user` | Read and Write | read which account the token belongs to, and create a repository under **your own** account |
4. Click **Generate Token**.
![Step 3: the token form with the name, the access choice and three permissions set to Read and Write](img/gitea-3-token-form.svg)
> **Only creating projects in organizations?** Then `user` can stay at **Read**.
> `Read and Write` on `user` is needed only to create a repository under your
> own account; without it Gitea answers `required=[write:user]`.
### Step 4. Copy the token now
Gitea shows the new token **once**, in a green message at the top of the page
(1). Copy it (use the copy button if your version has one, or select the text
and press Ctrl+C, Cmd+C on a Mac) and keep it for
[step 3 of this guide](#3-give-the-tokens-to-repofoundry). The new token is now
in the list below it (2), with its permissions, but the list never shows its
value again.
If you lose the value, delete that token and generate a new one.
![Step 4: the green message with the new token and a Copy button, and the token in the list below](img/gitea-4-copy-token.svg)
---
## 2. Create a GitHub personal access token (classic)
Skip this part when you will not create a GitHub repository (`USE_GITHUB=no` or
answering no when asked). `GITHUB_PAT` is then not needed.
Use a **classic** token. RepoFoundry has not been tested with fine-grained
tokens, because GitHub documents no fine-grained permission for creating a
repository.
### Step 1. Open your settings
Sign in to GitHub. Click your **profile picture** in the top-right corner (1),
then **Settings** (2).
![Step 1: the profile menu in the top-right corner, with Settings highlighted](img/github-1-open-settings.svg)
### Step 2. Open "Developer settings"
In the left sidebar, scroll to the bottom and click **Developer settings** (1).
It is the last entry.
![Step 2: Developer settings at the bottom of the left sidebar](img/github-2-developer-settings.svg)
### Step 3. Choose "Tokens (classic)"
1. In the left sidebar, open **Personal access tokens** and click **Tokens
(classic)** (1).
2. Click **Generate new token** (2).
3. In the menu that opens, choose **Generate new token (classic)** (3). Not
the first entry, which is the fine-grained kind.
GitHub may ask you to confirm your password or a two-factor code. Do that
yourself; nobody else should type it.
![Step 3: Tokens (classic) in the sidebar, and Generate new token (classic) in the button menu](img/github-3-tokens-classic.svg)
> Direct address: `https://github.com/settings/tokens/new`
### Step 4. Fill in the form
1. **Note:** a name you will recognise later, for example `RepoFoundry`.
2. **Expiration:** pick a date. A shorter life limits the damage if the token
leaks; you then create a new token when it ends.
3. **Select scopes:** tick the scopes below and nothing else.
| Scope | Tick it when | Why |
| --- | --- | --- |
| `repo` | always (private or public projects) | creates the repository, and is the password Gitea uses to push the mirror |
| `public_repo` instead of `repo` | **only** public projects | enough to create and push a public repository; ticking `repo` already includes it |
| `read:org` | only if the script says you do not belong to your organization | lets the script check your membership of an organization owner |
Ticking `repo` also ticks its five sub-scopes (`repo:status`,
`repo_deployment`, `public_repo`, `repo:invite`, `security_events`); that is
expected. Leave `workflow`, `write:packages` and the rest unticked.
4. Scroll down and click **Generate token** (4).
![Step 4: the new token form with a note, an expiration, the repo scope ticked and the Generate token button](img/github-4-token-form.svg)
> **`read:org` and `admin:org`.** The README records that the check worked with
> a token that had `repo` and `admin:org`, and that `read:org` alone is
> untested. Try `read:org` first. `admin:org` gives far more power than
> RepoFoundry needs, so use it only if `read:org` is not enough.
### Step 5. Copy the token now
GitHub shows the token **once**, in a green box at the top (1). Click the copy
icon next to it. It starts with `ghp_`. Keep it for
[step 3 of this guide](#3-give-the-tokens-to-repofoundry).
If the owner of the new repository is an organization that uses SAML single
sign-on, the token must also be authorised for that organization: in the token
list click **Configure SSO** (2) next to the token and then **Authorize**
for the organization.
![Step 5: the green box with the new token and a copy icon, and Configure SSO in the token list](img/github-5-copy-token.svg)
---
## 3. Give the tokens to RepoFoundry
Choose one way. Both keep the token out of `config.env` (a token there is
rejected).
**Option A: type it when asked (nothing is stored).** Do nothing in advance.
RepoFoundry asks for `Gitea access token` at the start, and for
`GitHub personal access token` and `GitHub account name` once you choose
GitHub. What you type is not shown on the screen. Paste only the token, with no
spaces, no line break and no quote marks.
**Option B: keep them in `.env` (convenient, plain text on disk).** In the
RepoFoundry folder:
```bash
cp .env.example .env
chmod 600 .env # Linux and macOS: only you can read it
```
Open `.env` and fill in the values; replace the text in angle brackets and
remove the brackets:
```text
GITEA_TOKEN=<the Gitea token>
GITHUB_PAT=<the GitHub token>
GITHUB_USER=<your GitHub account name>
```
`GITHUB_PAT` and `GITHUB_USER` are needed only when you create a GitHub
repository. Git ignores `.env`. Never commit it, send it, or paste it into a
chat or an issue.
Then check the setup with a dry run, which only reads from the hosts and
creates nothing:
```bash
src/create-project.sh
```
---
## 4. Keep the tokens safe
- A token is a password. Anyone who has it can do what it allows.
- Give each token only the access in this guide, and an expiration date.
- Do not paste a token in a command line, a URL, a commit, an issue or a chat.
- If a token may have leaked, delete it at once and make a new one:
- Gitea: **Settings**, **Applications**, then **Delete** on that token.
- GitHub: **Settings**, **Developer settings**, **Personal access tokens**,
**Tokens (classic)**, then **Delete** on that token.
- When a token expires, create a new one the same way and replace it in `.env`.
---
## 5. If something goes wrong
| What you see | Likely cause | What to do |
| --- | --- | --- |
| `authentication failed: the token is missing, expired or invalid` | the token was mistyped, has expired or was deleted | create a new token and use it |
| `the token is valid but not allowed to do this (check its scopes)` | a permission is missing | Gitea: set `organization`, `repository` and `user` to **Read and Write**. GitHub: tick `repo` |
| Gitea answers `required=[write:user]` | the project is being created under your own account | set `user` to **Read and Write** |
| `Gitea owner '...' is neither your account (...) nor an organization the token can see` | the token is limited to **Public only**, or the owner name is wrong | make the token **All (public, private, and limited)**, or fix the owner |
| `GitHub owner '...' is neither your account (...) nor an organization you belong to (or the token lacks the read:org scope)` | the token cannot see the organization | tick `read:org`, and authorise the token for the organization if it uses single sign-on |
| the pasted token is refused again and again | spaces, a line break or quote marks came with the paste | copy it again and paste only the token |
| `GITHUB_USER is '...' but the token belongs to '...'` | a warning: the name in `.env` is not the account of the token | no action needed; RepoFoundry uses the account the token belongs to |
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

+1
View File
@@ -19,6 +19,7 @@ create_all() {
add_framework add_framework
install_framework install_framework
copy_templates copy_templates
exclude_framework_files
create_env_file create_env_file
} }
+6 -2
View File
@@ -8,7 +8,7 @@
# shellcheck disable=SC2034 # read and written by the other library files # shellcheck disable=SC2034 # read and written by the other library files
readonly PROJECT_NAME="${REPOFOUNDRY_NAME:-RepoFoundry}" readonly PROJECT_NAME="${REPOFOUNDRY_NAME:-RepoFoundry}"
readonly VERSION="0.3.0" readonly VERSION="0.3.2"
readonly EXIT_FAILURE=1 readonly EXIT_FAILURE=1
readonly EXIT_USAGE=2 readonly EXIT_USAGE=2
readonly MAX_VALUE_LENGTH=2048 readonly MAX_VALUE_LENGTH=2048
@@ -30,8 +30,12 @@ readonly HINT_LICENSE="use a Gitea license key (letters, digits, '.', '+' or '-'
readonly HINT_DIRECTORY="must not be empty, start with '-' or contain control characters" readonly HINT_DIRECTORY="must not be empty, start with '-' or contain control characters"
readonly HINT_TOKEN="8 to 255 letters, digits or _ . ~ + / = -" readonly HINT_TOKEN="8 to 255 letters, digits or _ . ~ + / = -"
readonly ENV_FILE_NAME=".env" readonly ENV_FILE_NAME=".env"
# What the framework installs into the new project that git must not list: the
# folders of the skills and the copy of AGENTS.md, anchored to the project
# root. They go into .git/info/exclude of the new project.
readonly FRAMEWORK_EXCLUDES=(/.claude /.agents /AGENTS.md)
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" "Project .env") "Local project" "Framework" "Skills and hooks" "Templates" "Git excludes" "Project .env")
# 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
+2 -13
View File
@@ -26,19 +26,8 @@ env_file_key_list() {
# file: the entry goes into .git/info/exclude, which is never committed. It # file: the entry goes into .git/info/exclude, which is never committed. It
# does nothing when .env is already ignored. # does nothing when .env is already ignored.
exclude_env_file() { exclude_env_file() {
local dir="$1" gitdir exclude local dir="$1"
if git_project "$dir" check-ignore -q -- "$ENV_FILE_NAME"; then exclude_from_git "$dir" "RepoFoundry: the credentials file of this project" "$ENV_FILE_NAME" ||
return 0
fi
gitdir="$(git_project "$dir" rev-parse --absolute-git-dir)"
exclude="$gitdir/info/exclude"
mkdir -p -- "$gitdir/info"
# Start on a fresh line when the file does not end with one.
if [[ -s $exclude && -n "$(tail -c 1 -- "$exclude")" ]]; then
printf '\n' >>"$exclude"
fi
printf '%s\n' "# RepoFoundry: the credentials file of this project" "$ENV_FILE_NAME" >>"$exclude"
git_project "$dir" check-ignore -q -- "$ENV_FILE_NAME" ||
die "could not make git ignore $ENV_FILE_NAME in $dir; nothing was written to it" die "could not make git ignore $ENV_FILE_NAME in $dir; nothing was written to it"
} }
+37 -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: is_framework_skipped, init_framework_submodules, add_framework, run_framework_script, install_skills, install_hooks, install_framework, copy_template, copy_templates # Provides: is_framework_skipped, init_framework_submodules, add_framework, run_framework_script, install_skills, install_hooks, install_framework, copy_template, copy_templates, exclude_framework_files
# is_framework_skipped: succeed when the framework steps are left out because # is_framework_skipped: succeed when the framework steps are left out because
# SSH to Gitea is not available (the Maintainer agreed to that). # SSH to Gitea is not available (the Maintainer agreed to that).
@@ -165,3 +165,39 @@ copy_templates() {
notes="$notes; ${STATE[template_note]}" notes="$notes; ${STATE[template_note]}"
finish_step "$label" "created" "($notes)" finish_step "$label" "created" "($notes)"
} }
# exclude_framework_files: make git ignore .claude, .agents and AGENTS.md in
# the new project, through its .git/info/exclude (never .gitignore, so no
# tracked file changes and nothing is committed). Only when the framework
# steps ran. A path git already tracks stays tracked: an exclusion does not
# apply to it, so the step names it.
exclude_framework_files() {
local label="Git excludes" dir="${PROJECT[directory]}" entry path tracked=() note written
if is_framework_skipped; then
finish_step "$label" "skipped" "(no SSH access to Gitea)"
return 0
fi
begin_step "$label"
exclude_from_git "$dir" "RepoFoundry: the files installed from the framework" "${FRAMEWORK_EXCLUDES[@]}" ||
die "could not make git ignore the framework files in $dir; check $dir/.git/info/exclude"
written="$REPLY"
for entry in "${FRAMEWORK_EXCLUDES[@]}"; do
path="${entry#/}"
if [[ -n "$(git_project "$dir" ls-files -- "$path")" ]]; then
tracked+=("$path")
fi
done
if ((written == 0)); then
note="${FRAMEWORK_EXCLUDES[*]} already excluded"
else
note="${FRAMEWORK_EXCLUDES[*]} added to .git/info/exclude"
fi
if ((${#tracked[@]})); then
note="$note; git tracks ${tracked[*]}, so it is not ignored"
fi
if ((written == 0)); then
finish_step "$label" "reused" "($note)"
else
finish_step "$label" "created" "($note)"
fi
}
+35 -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: git_project, fetch_origin # Provides: git_project, exclude_from_git, fetch_origin
# git_project DIR ARGS...: run git in DIR. Prompts are switched off and stdin # git_project DIR ARGS...: run git in DIR. Prompts are switched off and stdin
# is closed (git must not eat the answers meant for later prompts), so a # is closed (git must not eat the answers meant for later prompts), so a
@@ -16,6 +16,40 @@ git_project() {
git -C "$dir" "$@" </dev/null git -C "$dir" "$@" </dev/null
} }
# exclude_from_git DIR COMMENT ENTRY...: make git ignore each ENTRY in DIR
# without touching a tracked file: the entries go into .git/info/exclude,
# which is never committed. An entry that already takes effect (checked by its
# pattern, so a tracked path counts) is left out, and with none left nothing
# is written. The comment line goes above the entries that are written. The
# number of entries written is left in REPLY; the return status is 1 when an
# entry still does not take effect afterwards.
exclude_from_git() {
local dir="$1" comment="$2" gitdir exclude entry path missing=()
shift 2
for entry in "$@"; do
path="${entry#/}"
if ! git_project "$dir" check-ignore --no-index -q -- "${path%/}"; then
missing+=("$entry")
fi
done
REPLY="${#missing[@]}"
if ((REPLY == 0)); then
return 0
fi
gitdir="$(git_project "$dir" rev-parse --absolute-git-dir)"
exclude="$gitdir/info/exclude"
mkdir -p -- "$gitdir/info"
# Start on a fresh line when the file does not end with one.
if [[ -s $exclude && -n "$(tail -c 1 -- "$exclude")" ]]; then
printf '\n' >>"$exclude"
fi
printf '%s\n' "# $comment" "${missing[@]}" >>"$exclude"
for entry in "${missing[@]}"; do
path="${entry#/}"
git_project "$dir" check-ignore --no-index -q -- "${path%/}" || return 1
done
}
# fetch_origin DIR: fetch the Gitea repository into the project. Over SSH the # fetch_origin DIR: fetch the Gitea repository into the project. Over SSH the
# user's key is used. Over HTTPS the token reaches git through a private # user's key is used. Over HTTPS the token reaches git through a private
# GIT_ASKPASS helper and the environment of this one command: it is never part # GIT_ASKPASS helper and the environment of this one command: it is never part
+1 -5
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: is_reused, repo_owner, repo_url, gitea_host, origin_protocol, origin_url, github_remote_url, framework_url # Provides: is_reused, repo_owner, repo_url, gitea_host, origin_protocol, origin_url, framework_url
# is_reused HOST: succeed if the existing repository on HOST will be reused. # is_reused HOST: succeed if the existing repository on HOST will be reused.
is_reused() { is_reused() {
@@ -54,10 +54,6 @@ origin_url() {
fi fi
} }
github_remote_url() {
printf '%s/%s/%s.git' "${CONFIG[GITHUB_WEB_URL]}" "${PROJECT[github_owner]}" "${PROJECT[name]}"
}
# framework_url: where the framework submodule comes from (always SSH). # framework_url: where the framework submodule comes from (always SSH).
framework_url() { framework_url() {
printf 'ssh://git@%s:%s/%s.git' "$(gitea_host)" "${CONFIG[GITEA_SSH_PORT]}" \ printf 'ssh://git@%s:%s/%s.git' "$(gitea_host)" "${CONFIG[GITEA_SSH_PORT]}" \
+4 -5
View File
@@ -71,8 +71,10 @@ checkout_gitea_history() {
fi fi
} }
# create_local_project: the directory, the git repository on main, the # create_local_project: the directory, the git repository on main, the origin
# remotes and, when a license applies, the license history. No commit is made. # remote and, when a license applies, the license history. No commit is made.
# Gitea is the only remote: a push to it reaches GitHub through the push
# mirror, so no github remote is added (and none that exists is removed).
create_local_project() { create_local_project() {
local label="Local project" dir="${PROJECT[directory]}" local label="Local project" dir="${PROJECT[directory]}"
begin_step "$label" begin_step "$label"
@@ -82,9 +84,6 @@ create_local_project() {
git_project "$dir" symbolic-ref HEAD "refs/heads/$DEFAULT_BRANCH" git_project "$dir" symbolic-ref HEAD "refs/heads/$DEFAULT_BRANCH"
fi fi
ensure_remote "$dir" origin "$(origin_url)" ensure_remote "$dir" origin "$(origin_url)"
if ((PROJECT[has_github])); then
ensure_remote "$dir" github "$(github_remote_url)"
fi
if [[ -n ${PROJECT[license]} ]]; then if [[ -n ${PROJECT[license]} ]]; then
checkout_gitea_history "$dir" checkout_gitea_history "$dir"
fi fi
+1
View File
@@ -51,6 +51,7 @@ print_plan() {
say "$(printf ' %-18s: %s' "Framework" "add $(framework_url) as a submodule")" say "$(printf ' %-18s: %s' "Framework" "add $(framework_url) as a submodule")"
say "$(printf ' %-18s: %s' "Skills and hooks" "install once; plan gate $(yes_no "${PROJECT[is_plan_gate_enabled]}")")" say "$(printf ' %-18s: %s' "Skills and hooks" "install once; plan gate $(yes_no "${PROJECT[is_plan_gate_enabled]}")")"
say "$(printf ' %-18s: %s' "Templates" "AGENTS.md and docs/artifact-registry.md (you are asked before a file is replaced)")" say "$(printf ' %-18s: %s' "Templates" "AGENTS.md and docs/artifact-registry.md (you are asked before a file is replaced)")"
say "$(printf ' %-18s: %s' "Git excludes" "${FRAMEWORK_EXCLUDES[*]} go into .git/info/exclude (no tracked file changes)")"
else else
say "$(printf ' %-18s: %s' "Framework" "NOT possible without SSH to Gitea; you will be asked whether to go on without it")" say "$(printf ' %-18s: %s' "Framework" "NOT possible without SSH to Gitea; you will be asked whether to go on without it")"
fi fi
+1
View File
@@ -291,6 +291,7 @@ echo done'
local exclude local exclude
exclude="$(cat "$WORK/p/.git/info/exclude")" exclude="$(cat "$WORK/p/.git/info/exclude")"
assert_contains "old entry kept" "$exclude" "build/" assert_contains "old entry kept" "$exclude" "build/"
assert_contains "comment line" "$exclude" "# RepoFoundry: the credentials file of this project"
assert_eq "entry added once" "1" "$(grep -c '^\.env$' "$WORK/p/.git/info/exclude")" assert_eq "entry added once" "1" "$(grep -c '^\.env$' "$WORK/p/.git/info/exclude")"
assert_eq "old entry still on its own line" "1" "$(grep -c '^build/$' "$WORK/p/.git/info/exclude")" assert_eq "old entry still on its own line" "1" "$(grep -c '^build/$' "$WORK/p/.git/info/exclude")"
} }
+127 -8
View File
@@ -35,7 +35,7 @@ readonly SSH_FRAMEWORK_URL="ssh://git@git.example.test:10022/TirSystem/SQA-QC-Fr
# ----------------------------------------------------- directory and remotes # ----------------------------------------------------- directory and remotes
test_local_project_gets_credential_free_remotes_and_the_license_history() { test_local_project_gets_the_origin_remote_only_and_the_license_history() {
setup_hosts setup_hosts
local dir="$WORK/project" local dir="$WORK/project"
local_answers "$dir" y n local_answers "$dir" y n
@@ -44,7 +44,9 @@ test_local_project_gets_credential_free_remotes_and_the_license_history() {
assert_file_exists "directory created" "$dir/.git" assert_file_exists "directory created" "$dir/.git"
assert_eq "branch is main" "main" "$(project_git "$dir" symbolic-ref --short HEAD)" assert_eq "branch is main" "main" "$(project_git "$dir" symbolic-ref --short HEAD)"
assert_eq "origin over SSH, no credential" "ssh://git@git.example.test:10022/TirSystem/my-app.git" "$(project_git "$dir" config --get remote.origin.url)" assert_eq "origin over SSH, no credential" "ssh://git@git.example.test:10022/TirSystem/my-app.git" "$(project_git "$dir" config --get remote.origin.url)"
assert_eq "github remote over HTTPS, no credential" "https://github.com/acme-org/my-app.git" "$(project_git "$dir" config --get remote.github.url)" assert_eq "origin is the only remote, also with GitHub" "origin" "$(project_git "$dir" remote)"
assert_eq "no github remote" "" "$(project_git "$dir" config --get remote.github.url || true)"
assert_not_contains "no GitHub address in the git config" "$(cat "$dir/.git/config")" "github.com"
assert_eq "the license commit is the whole history" "1" "$(project_git "$dir" rev-list --count HEAD)" assert_eq "the license commit is the whole history" "1" "$(project_git "$dir" rev-list --count HEAD)"
assert_eq "it is the Gitea commit" "Initial commit" "$(project_git "$dir" log -1 --format=%s)" assert_eq "it is the Gitea commit" "Initial commit" "$(project_git "$dir" log -1 --format=%s)"
assert_file_exists "LICENSE from Gitea" "$dir/LICENSE" assert_file_exists "LICENSE from Gitea" "$dir/LICENSE"
@@ -53,7 +55,7 @@ test_local_project_gets_credential_free_remotes_and_the_license_history() {
assert_contains "reported" "$OUT" "Local project : created $dir (origin over SSH)" assert_contains "reported" "$OUT" "Local project : created $dir (origin over SSH)"
} }
test_gitea_only_project_has_no_github_remote_and_no_commit() { test_gitea_only_project_has_the_origin_remote_only_and_no_commit() {
setup_hosts setup_hosts
printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env" printf 'GITEA_TOKEN=%s\n' "$FAKE_GITEA_TOKEN" >"$WORK/.env"
local dir="$WORK/project" local dir="$WORK/project"
@@ -61,7 +63,7 @@ test_gitea_only_project_has_no_github_remote_and_no_commit() {
run_apply "$LOCAL_ANSWERS"$'y\n' run_apply "$LOCAL_ANSWERS"$'y\n'
assert_status "apply" 0 "$STATUS" assert_status "apply" 0 "$STATUS"
assert_eq "origin" "ssh://git@git.example.test:10022/TirSystem/my-app.git" "$(project_git "$dir" config --get remote.origin.url)" assert_eq "origin" "ssh://git@git.example.test:10022/TirSystem/my-app.git" "$(project_git "$dir" config --get remote.origin.url)"
assert_eq "no github remote" "" "$(project_git "$dir" config --get remote.github.url || true)" assert_eq "origin is the only remote" "origin" "$(project_git "$dir" remote)"
assert_file_missing "no license file" "$dir/LICENSE" assert_file_missing "no license file" "$dir/LICENSE"
assert_eq "no commit was made" "0" "$(project_git "$dir" rev-list --all --count)" assert_eq "no commit was made" "0" "$(project_git "$dir" rev-list --all --count)"
assert_eq "no token in any file" "" "$(files_with_secret "$dir")" assert_eq "no token in any file" "" "$(files_with_secret "$dir")"
@@ -109,6 +111,20 @@ test_a_file_that_would_be_overwritten_by_the_license_history_is_kept() {
assert_contains "later steps not attempted" "$OUT" "Framework : not attempted" assert_contains "later steps not attempted" "$OUT" "Framework : not attempted"
} }
test_a_github_remote_from_an_earlier_version_is_left_alone() {
setup_hosts
local dir="$WORK/project"
mkdir -p "$dir"
project_git "$dir" init -q
project_git "$dir" remote add github https://github.com/acme-org/my-app.git
local_answers "$dir" y n
run_apply "$LOCAL_ANSWERS"$'y\ny\n'
assert_status "apply" 0 "$STATUS"
assert_eq "the github remote is kept as it was" "https://github.com/acme-org/my-app.git" "$(project_git "$dir" config --get remote.github.url)"
assert_eq "origin was added beside it" "ssh://git@git.example.test:10022/TirSystem/my-app.git" "$(project_git "$dir" config --get remote.origin.url)"
assert_eq "no other remote" $'github\norigin' "$(project_git "$dir" remote | sort)"
}
test_a_remote_with_another_address_is_never_replaced() { test_a_remote_with_another_address_is_never_replaced() {
setup_hosts setup_hosts
local dir="$WORK/project" local dir="$WORK/project"
@@ -266,6 +282,105 @@ test_existing_template_targets_are_replaced_only_after_a_yes() {
assert_contains "reported" "$OUT" "kept existing AGENTS.md; copied docs/artifact-registry.md" assert_contains "reported" "$OUT" "kept existing AGENTS.md; copied docs/artifact-registry.md"
} }
# ------------------------------------------------------------- git excludes
# exclude_count FILE LINE: how many lines of FILE are exactly LINE.
exclude_count() {
grep -cxF -e "$2" "$1" || true
}
test_the_framework_files_are_ignored_by_git_and_nothing_else_changes() {
setup_hosts
local dir="$WORK/project" exclude path listing
exclude="$dir/.git/info/exclude"
local_answers "$dir" y n
# create now, and create the project's .env as well
run_apply "$LOCAL_ANSWERS"$'y\ny\n'
assert_status "apply" 0 "$STATUS"
assert_contains "reported" "$OUT" "Git excludes : created (/.claude /.agents /AGENTS.md added to .git/info/exclude)"
for path in /.claude /.agents /AGENTS.md; do
assert_eq "one entry for $path" "1" "$(exclude_count "$exclude" "$path")"
done
for path in .claude .agents AGENTS.md .claude/skills/coding-conventions/SKILL.md .agents/skills/.framework-skills; do
check
if ! project_git "$dir" check-ignore -q -- "$path"; then
fail "$path is not ignored by git"
fi
done
for path in framework .gitmodules docs/artifact-registry.md; do
check
if project_git "$dir" check-ignore -q --no-index -- "$path"; then
fail "$path is ignored by git but is part of the project"
fi
done
listing="$(project_git "$dir" status --porcelain)"
assert_not_contains ".claude is not listed" "$listing" ".claude"
assert_not_contains ".agents is not listed" "$listing" ".agents"
assert_not_contains "AGENTS.md is not listed" "$listing" "AGENTS.md"
assert_contains "the submodule is listed" "$listing" "framework"
assert_contains "the registry is listed" "$listing" "docs/"
assert_file_missing "no .gitignore is written" "$dir/.gitignore"
assert_eq "nothing was committed" "1" "$(project_git "$dir" rev-list --count HEAD)"
assert_eq "the .env keeps its own entry, once" "1" "$(exclude_count "$exclude" ".env")"
assert_contains "the .env comment is unchanged" "$(cat "$exclude")" "# RepoFoundry: the credentials file of this project"
}
test_the_git_excludes_are_written_once_and_keep_the_existing_lines() {
local dir="$WORK/p" exclude="$WORK/p/.git/info/exclude" path
mkdir -p "$dir"
project_git "$dir" init -q
printf 'build/' >"$exclude" # no trailing newline
run_lib "" 'PROJECT[directory]="'"$dir"'"
exclude_framework_files
echo "${STEP_STATUS["Git excludes"]}"
exclude_framework_files
echo "${STEP_STATUS["Git excludes"]} ${STEP_DETAIL["Git excludes"]}"'
assert_status "run" 0 "$STATUS"
assert_eq "created, then reused" $'created\nreused (/.claude /.agents /AGENTS.md already excluded)' "$OUT"
assert_eq "the old line is kept on its own line" "1" "$(exclude_count "$exclude" "build/")"
for path in /.claude /.agents /AGENTS.md; do
assert_eq "entry for $path written once" "1" "$(exclude_count "$exclude" "$path")"
done
assert_eq "one comment line" "1" "$(exclude_count "$exclude" "# RepoFoundry: the files installed from the framework")"
}
test_nothing_is_written_for_paths_git_already_ignores() {
local dir="$WORK/p" exclude="$WORK/p/.git/info/exclude"
mkdir -p "$dir"
project_git "$dir" init -q
printf '.claude\n.agents\nAGENTS.md\n' >"$dir/.gitignore"
run_lib "" 'PROJECT[directory]="'"$dir"'"
exclude_framework_files
echo "${STEP_STATUS["Git excludes"]}"'
assert_status "run" 0 "$STATUS"
assert_eq "reused" "reused" "$OUT"
assert_eq "no entry written" "0" "$(grep -c '^/' "$exclude" || true)"
assert_eq "no comment written" "0" "$(exclude_count "$exclude" "# RepoFoundry: the files installed from the framework")"
}
test_a_tracked_framework_file_stays_tracked_and_is_named() {
local dir="$WORK/p"
mkdir -p "$dir/.agents" "$dir/.claude"
project_git "$dir" init -q
printf 'mine\n' >"$dir/AGENTS.md"
printf 'x\n' >"$dir/.agents/keep"
printf 'y\n' >"$dir/.claude/new" # not tracked
project_git "$dir" add AGENTS.md .agents/keep
project_git "$dir" commit -q -m seed
run_lib "" 'PROJECT[directory]="'"$dir"'"
exclude_framework_files
echo "${STEP_STATUS["Git excludes"]} ${STEP_DETAIL["Git excludes"]}"'
assert_status "run" 0 "$STATUS"
assert_contains "names the tracked paths" "$OUT" "git tracks .agents AGENTS.md, so it is not ignored"
assert_eq "both are still tracked" $'.agents/keep\nAGENTS.md' "$(project_git "$dir" ls-files)"
assert_eq "the content is unchanged" "mine" "$(cat "$dir/AGENTS.md")"
assert_eq "nothing is staged or modified" "" "$(project_git "$dir" status --porcelain)"
check
if ! project_git "$dir" check-ignore -q -- .claude; then
fail ".claude, which git does not track, is not ignored"
fi
}
# ----------------------------------------------------------- SSH and errors # ----------------------------------------------------------- SSH and errors
test_without_ssh_the_run_stops_unless_the_framework_is_skipped() { test_without_ssh_the_run_stops_unless_the_framework_is_skipped() {
@@ -295,6 +410,9 @@ test_without_ssh_the_framework_steps_are_skipped_after_a_yes() {
assert_contains "framework skipped" "$OUT" "Framework : skipped (no SSH access to Gitea)" assert_contains "framework skipped" "$OUT" "Framework : skipped (no SSH access to Gitea)"
assert_contains "skills and hooks skipped" "$OUT" "Skills and hooks : skipped (no SSH access to Gitea)" assert_contains "skills and hooks skipped" "$OUT" "Skills and hooks : skipped (no SSH access to Gitea)"
assert_contains "templates skipped" "$OUT" "Templates : skipped (no SSH access to Gitea)" assert_contains "templates skipped" "$OUT" "Templates : skipped (no SSH access to Gitea)"
assert_contains "git excludes skipped" "$OUT" "Git excludes : skipped (no SSH access to Gitea)"
assert_not_contains "nothing excluded: .claude" "$(cat "$dir/.git/info/exclude")" "/.claude"
assert_not_contains "nothing excluded: AGENTS.md" "$(cat "$dir/.git/info/exclude")" "/AGENTS.md"
assert_file_missing "no submodule" "$dir/.gitmodules" assert_file_missing "no submodule" "$dir/.gitmodules"
assert_file_missing "no AGENTS.md" "$dir/AGENTS.md" assert_file_missing "no AGENTS.md" "$dir/AGENTS.md"
} }
@@ -349,6 +467,7 @@ test_the_dry_run_plan_describes_the_local_steps_and_creates_nothing() {
assert_contains "framework" "$OUT" "Framework : add $SSH_FRAMEWORK_URL as a submodule" assert_contains "framework" "$OUT" "Framework : add $SSH_FRAMEWORK_URL as a submodule"
assert_contains "skills and hooks" "$OUT" "Skills and hooks : install once; plan gate yes" assert_contains "skills and hooks" "$OUT" "Skills and hooks : install once; plan gate yes"
assert_contains "templates" "$OUT" "Templates : AGENTS.md and docs/artifact-registry.md (you are asked before a file is replaced)" assert_contains "templates" "$OUT" "Templates : AGENTS.md and docs/artifact-registry.md (you are asked before a file is replaced)"
assert_contains "git excludes" "$OUT" "Git excludes : /.claude /.agents /AGENTS.md go into .git/info/exclude (no tracked file changes)"
assert_file_missing "nothing created" "$dir" assert_file_missing "nothing created" "$dir"
} }
@@ -362,6 +481,7 @@ test_the_plan_marks_an_existing_directory_and_missing_ssh() {
run_dry "$LOCAL_ANSWERS" run_dry "$LOCAL_ANSWERS"
assert_contains "existing directory" "$OUT" "use the existing directory $dir, which has files (you will be asked)" assert_contains "existing directory" "$OUT" "use the existing directory $dir, which has files (you will be asked)"
assert_contains "no SSH" "$OUT" "NOT possible without SSH to Gitea; you will be asked whether to go on without it" assert_contains "no SSH" "$OUT" "NOT possible without SSH to Gitea; you will be asked whether to go on without it"
assert_not_contains "no git excludes without the framework" "$OUT" "Git excludes"
assert_contains "HTTPS origin" "$OUT" "will use HTTPS (SSH test: failed" assert_contains "HTTPS origin" "$OUT" "will use HTTPS (SSH test: failed"
} }
@@ -379,16 +499,15 @@ test_a_project_path_that_is_a_file_is_refused_in_the_preflight() {
test_remote_addresses_are_built_from_the_configuration() { test_remote_addresses_are_built_from_the_configuration() {
run_lib "" 'CONFIG[GITEA_URL]=https://git.example.test/sub run_lib "" 'CONFIG[GITEA_URL]=https://git.example.test/sub
CONFIG[GITEA_SSH_PORT]=2222 CONFIG[FRAMEWORK_REPO]=Org/Fw CONFIG[GITHUB_WEB_URL]=https://github.com CONFIG[GITEA_SSH_PORT]=2222 CONFIG[FRAMEWORK_REPO]=Org/Fw
PROJECT[gitea_owner]=TirSystem PROJECT[github_owner]=acme PROJECT[name]=my-app PROJECT[gitea_owner]=TirSystem PROJECT[name]=my-app
STATE[is_ssh_ok]=1 STATE[is_ssh_ok]=1
origin_url; echo origin_url; echo
STATE[is_ssh_ok]=0 STATE[is_ssh_ok]=0
origin_url; echo origin_url; echo
github_remote_url; echo
framework_url; echo framework_url; echo
gitea_host; echo' gitea_host; echo'
assert_eq "addresses" $'ssh://git@git.example.test:2222/TirSystem/my-app.git\nhttps://git.example.test/sub/TirSystem/my-app.git\nhttps://github.com/acme/my-app.git\nssh://git@git.example.test:2222/Org/Fw.git\ngit.example.test' "$OUT" assert_eq "addresses" $'ssh://git@git.example.test:2222/TirSystem/my-app.git\nhttps://git.example.test/sub/TirSystem/my-app.git\nssh://git@git.example.test:2222/Org/Fw.git\ngit.example.test' "$OUT"
} }
test_the_https_fetch_hands_the_token_over_through_the_environment_only() { test_the_https_fetch_hands_the_token_over_through_the_environment_only() {
+1 -1
View File
@@ -103,7 +103,7 @@ test_usage_errors() {
assert_contains "help shows exit codes" "$OUT" "Exit codes" assert_contains "help shows exit codes" "$OUT" "Exit codes"
run_cli "" --version run_cli "" --version
assert_status "version" 0 "$STATUS" assert_status "version" 0 "$STATUS"
assert_contains "version output" "$OUT" "RepoFoundry 0.3.0" assert_contains "version output" "$OUT" "RepoFoundry 0.3.2"
} }
test_warns_when_env_is_not_ignored_by_git() { test_warns_when_env_is_not_ignored_by_git() {