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 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
5 changed files with 154 additions and 6 deletions
+47 -4
View File
@@ -8,7 +8,8 @@ RepoFoundry (`src/create-project.sh`) sets up a new project in one run:
- and a **local project** with one credential-free remote, `origin` (Gitea), and the
[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
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
and owner (a user or an organization, separately on each host), shows a plan,
@@ -259,6 +260,7 @@ Plan:
Framework : add ssh://git@git.example.org:10022/Team/SQA-QC-Framework.git as a submodule
Skills and hooks : install once; plan gate no
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
@@ -272,7 +274,10 @@ Without `--apply` that is all that happens. With `--apply` the script asks
repository holds the license commit, that history;
5. the framework as the submodule `framework`;
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
hooks refuse commits on `main`.
@@ -298,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
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
The framework submodule is fetched over SSH on port **10022**
@@ -410,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 |
| 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 |
| 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 |
A partial run is reported like this:
@@ -453,6 +492,10 @@ web interface and the project directory by hand.
copy.
- **The framework needs SSH.** Without SSH access to Gitea the framework steps
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
macOS is an open follow-up.
@@ -484,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 |
| `repositories.sh` | creating the GitHub and Gitea repositories |
| `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 |
| `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 |
| `cli.sh` | usage text and option parsing |
+1 -1
View File
@@ -8,7 +8,7 @@
# shellcheck disable=SC2034 # read and written by the other library files
readonly PROJECT_NAME="${REPOFOUNDRY_NAME:-RepoFoundry}"
readonly VERSION="0.3.1"
readonly VERSION="0.3.2"
readonly EXIT_FAILURE=1
readonly EXIT_USAGE=2
readonly MAX_VALUE_LENGTH=2048
+1
View File
@@ -291,6 +291,7 @@ echo done'
local exclude
exclude="$(cat "$WORK/p/.git/info/exclude")"
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 "old entry still on its own line" "1" "$(grep -c '^build/$' "$WORK/p/.git/info/exclude")"
}
+104
View File
@@ -282,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"
}
# ------------------------------------------------------------- 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
test_without_ssh_the_run_stops_unless_the_framework_is_skipped() {
@@ -311,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 "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 "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 AGENTS.md" "$dir/AGENTS.md"
}
@@ -365,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 "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 "git excludes" "$OUT" "Git excludes : /.claude /.agents /AGENTS.md go into .git/info/exclude (no tracked file changes)"
assert_file_missing "nothing created" "$dir"
}
@@ -378,6 +481,7 @@ test_the_plan_marks_an_existing_directory_and_missing_ssh() {
run_dry "$LOCAL_ANSWERS"
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_not_contains "no git excludes without the framework" "$OUT" "Git excludes"
assert_contains "HTTPS origin" "$OUT" "will use HTTPS (SSH test: failed"
}
+1 -1
View File
@@ -103,7 +103,7 @@ test_usage_errors() {
assert_contains "help shows exit codes" "$OUT" "Exit codes"
run_cli "" --version
assert_status "version" 0 "$STATUS"
assert_contains "version output" "$OUT" "RepoFoundry 0.3.1"
assert_contains "version output" "$OUT" "RepoFoundry 0.3.2"
}
test_warns_when_env_is_not_ignored_by_git() {