Add the local project, framework submodule, hooks and templates
After the repositories and the mirror, the script now creates the local project: the directory, git on main, credential-free origin (and github) remotes, and the license history from Gitea. Then it adds the framework as a submodule over SSH, installs its skills and git hooks once (plan gate optional), and copies the AGENTS.md and artifact registry templates. Nothing is overwritten without a yes: an existing directory, core.hooksPath, AGENTS.md or docs/artifact-registry.md each ask first (default no). Without SSH the framework steps can only be skipped, after a yes. No commit is made in the new project. New optional config key FRAMEWORK_REPO. New library files git.sh, localproject.sh and framework.sh. Tests run real git against local bare repositories that stand in for Gitea and the framework, with a private git configuration (769 checks). The README is now the full guide. Task: MIL-003#1 Task: MIL-003#2 Task: MIL-003#3 Task: MIL-003#4 Task: MIL-003#5 Refs #15 Refs #16 Refs #17 Refs #18 Refs #19 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
# shellcheck shell=bash
|
||||
# shellcheck disable=SC2004,SC2034,SC2154 # shared state and arrays are declared in constants.sh
|
||||
# framework.sh - The SQA-QC-Framework in the new project: submodule, skills, hooks and templates.
|
||||
#
|
||||
# Part of create-project.sh: sourced by it, never run on its own.
|
||||
#
|
||||
# Provides: is_framework_skipped, add_framework, run_framework_script, install_skills, install_hooks, install_framework, copy_template, copy_templates
|
||||
|
||||
# is_framework_skipped: succeed when the framework steps are left out because
|
||||
# SSH to Gitea is not available (the Maintainer agreed to that).
|
||||
is_framework_skipped() {
|
||||
[[ ${STATE[skip_framework]:-0} == 1 ]]
|
||||
}
|
||||
|
||||
# add_framework: git submodule add of the framework as "framework". SSH is
|
||||
# needed for it; a failure says how to test the access.
|
||||
add_framework() {
|
||||
local label="Framework" dir="${PROJECT[directory]}" url err existing
|
||||
url="$(framework_url)"
|
||||
if is_framework_skipped; then
|
||||
finish_step "$label" "skipped" "(no SSH access to Gitea)"
|
||||
return 0
|
||||
fi
|
||||
begin_step "$label"
|
||||
if [[ -e $dir/framework ]]; then
|
||||
existing="$(git_project "$dir" config -f .gitmodules --get submodule.framework.url || true)"
|
||||
if [[ $existing != "$url" ]]; then
|
||||
die "'framework' already exists in $dir and is not the framework submodule ($url)"
|
||||
fi
|
||||
finish_step "$label" "reused" "$url (already a submodule)"
|
||||
return 0
|
||||
fi
|
||||
make_temp_file
|
||||
err="$REPLY"
|
||||
if ! git_project "$dir" submodule add -q "$url" framework 2>"$err"; then
|
||||
die "git could not add the framework from $url. Check the SSH access first: ssh -p ${CONFIG[GITEA_SSH_PORT]} -T git@$(gitea_host). Git said: $(head -n 2 "$err" | tr '\n' ' ')"
|
||||
fi
|
||||
finish_step "$label" "created" "$url"
|
||||
}
|
||||
|
||||
# run_framework_script DIR SCRIPT [ARG...]: run one of the framework's own
|
||||
# scripts inside the project. PROJECT_ROOT is set explicitly so that a
|
||||
# PROJECT_ROOT in the caller's environment cannot point it elsewhere.
|
||||
run_framework_script() {
|
||||
local dir="$1" script="$2" out abs
|
||||
shift 2
|
||||
abs="$(cd "$dir" && pwd)"
|
||||
make_temp_file
|
||||
out="$REPLY"
|
||||
if ! (cd "$abs" && env PROJECT_ROOT="$abs" bash "framework/scripts/$script" "$@") >"$out" 2>&1; then
|
||||
die "the framework script $script failed: $(tail -n 3 "$out" | tr '\n' ' ')"
|
||||
fi
|
||||
}
|
||||
|
||||
# install_skills DIR: install the framework's skills once. The installer is
|
||||
# safe to repeat but copies everything again, so a project that already has
|
||||
# them is left alone.
|
||||
install_skills() {
|
||||
local dir="$1"
|
||||
if [[ -f $dir/.agents/skills/.framework-skills && -f $dir/.claude/skills/.framework-skills ]]; then
|
||||
STATE[skills_note]="skills already installed"
|
||||
return 0
|
||||
fi
|
||||
run_framework_script "$dir" install-skills.sh
|
||||
STATE[skills_note]="skills installed"
|
||||
}
|
||||
|
||||
# install_hooks DIR: point core.hooksPath at the framework's hooks, and turn
|
||||
# on the plan gate if chosen. A different hooks path that is already set is
|
||||
# only replaced after a yes.
|
||||
install_hooks() {
|
||||
local dir="$1" current global gate=() gate_enabled
|
||||
if ((PROJECT[is_plan_gate_enabled])); then
|
||||
gate=(--enable-plan-gate)
|
||||
fi
|
||||
# Both settings are normally unset; git config exits 1 then.
|
||||
current="$(git_project "$dir" config --local --get core.hooksPath || true)"
|
||||
global="$(git_project "$dir" config --global --get core.hooksPath || true)"
|
||||
gate_enabled="$(git_project "$dir" config --local --get planGate.enabled || true)"
|
||||
if [[ -z $current && -n $global ]]; then
|
||||
warn "your global core.hooksPath is '$global'; this project sets its own, which takes precedence here"
|
||||
fi
|
||||
if [[ -z $current ]]; then
|
||||
run_framework_script "$dir" install-git-hooks.sh "${gate[@]}"
|
||||
STATE[hooks_note]="hooks installed"
|
||||
elif [[ $current == framework/githooks ]]; then
|
||||
STATE[hooks_note]="hooks already installed"
|
||||
if ((PROJECT[is_plan_gate_enabled])) && [[ $gate_enabled != true ]]; then
|
||||
run_framework_script "$dir" install-git-hooks.sh "${gate[@]}"
|
||||
fi
|
||||
else
|
||||
prompt_yes_no "core.hooksPath is already '$current'. Replace it with framework/githooks" n
|
||||
if ((REPLY)); then
|
||||
run_framework_script "$dir" install-git-hooks.sh "${gate[@]}"
|
||||
STATE[hooks_note]="hooks installed (replaced '$current')"
|
||||
else
|
||||
STATE[hooks_note]="kept the existing hooks path '$current'"
|
||||
if ((PROJECT[is_plan_gate_enabled])); then
|
||||
warn "the plan gate is not enabled because the framework hooks were not installed"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# install_framework: skills, then hooks (and the plan gate). Each is done once.
|
||||
install_framework() {
|
||||
local label="Skills and hooks" dir="${PROJECT[directory]}" gate_state="plan gate off"
|
||||
if is_framework_skipped; then
|
||||
finish_step "$label" "skipped" "(no SSH access to Gitea)"
|
||||
return 0
|
||||
fi
|
||||
begin_step "$label"
|
||||
install_skills "$dir"
|
||||
install_hooks "$dir"
|
||||
if [[ $(git_project "$dir" config --local --get planGate.enabled || true) == true ]]; then
|
||||
gate_state="plan gate on"
|
||||
fi
|
||||
finish_step "$label" "created" "(${STATE[skills_note]}; ${STATE[hooks_note]}; $gate_state)"
|
||||
}
|
||||
|
||||
# copy_template DIR SOURCE TARGET: copy a framework template. An existing
|
||||
# target is only replaced after a yes. The result goes to STATE[template_note].
|
||||
copy_template() {
|
||||
local dir="$1" source="$2" target="$3"
|
||||
if [[ ! -f $dir/$source ]]; then
|
||||
die "the framework has no $source; is the submodule complete?"
|
||||
fi
|
||||
if [[ -e $dir/$target ]]; then
|
||||
prompt_yes_no "$target already exists. Replace it with the framework template" n
|
||||
if ! ((REPLY)); then
|
||||
STATE[template_note]="kept existing $target"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
cp -- "$dir/$source" "$dir/$target"
|
||||
STATE[template_note]="copied $target"
|
||||
}
|
||||
|
||||
# copy_templates: AGENTS.md and docs/artifact-registry.md from the framework.
|
||||
copy_templates() {
|
||||
local label="Templates" dir="${PROJECT[directory]}" notes=""
|
||||
if is_framework_skipped; then
|
||||
finish_step "$label" "skipped" "(no SSH access to Gitea)"
|
||||
return 0
|
||||
fi
|
||||
begin_step "$label"
|
||||
mkdir -p -- "$dir/docs"
|
||||
copy_template "$dir" framework/templates/AGENTS-template.md AGENTS.md
|
||||
notes="${STATE[template_note]}"
|
||||
copy_template "$dir" framework/templates/artifact-registry-template.md docs/artifact-registry.md
|
||||
notes="$notes; ${STATE[template_note]}"
|
||||
finish_step "$label" "created" "($notes)"
|
||||
}
|
||||
Reference in New Issue
Block a user