Skip to content

feat: bootstrap phase 9 requires an explicit licence choice - #29

Merged
TzuH-Hsu merged 12 commits into
mainfrom
feat/28-licence-phase
Sep 5, 2026
Merged

feat: bootstrap phase 9 requires an explicit licence choice#29
TzuH-Hsu merged 12 commits into
mainfrom
feat/28-licence-phase

Conversation

@TzuH-Hsu

@TzuH-Hsu TzuH-Hsu commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Summary

Adds bootstrap phase 9: forces an explicit licence choice before de-templating, and writes NOTICE on every answer that touches LICENSE. De-template renumbers 9 → 10.

Stacked PR — base is feat/26-security-phase (#27). Chain is #25#27 → this. Merge in that order; GitHub retargets automatically.

Related issue

Closes #28

Validation

  • L0 static — markdownlint, yamllint, offline lychee (48 OK, 0 errors), actionlint, gitleaks, check-skills, check-local-md, check-license-marker — all green
  • L1 unit — make test (placeholder), plus nine behavioural tests below
  • L2 integration — bash -n + shellcheck clean on both scripts; full --dry-run against this repo
  • L3 e2e / preview — see RISK

All nine licence paths were executed against scratch directories, never against this repo's LICENSE:

# Path Result
1 --license mit LICENSE line 3 = adopter; NOTICE created, 39 lines, retains the template's copyright
2 --license proprietary proprietary body written; NOT LEGAL ADVICE trailer present; NOTICE retains template copyright
3 --license defer LICENSE byte-identical; no NOTICE
4 closed stdin, no flags WARN stdin closed — deferring; LICENSE unchanged; no NOTICE
5 re-run after a decision LICENSE no longer carries the template's copyright line — leaving it alone
6 dirty LICENSE skipped, urgent manual step filed, file byte-identical
7 no LICENSE at all warn + urgent step, no crash under set -e
8 holder Smith & Jones a/s written intact — proves regeneration, not sed
9 --yes writes nothing; urgent step renders first, above an earlier-registered step

Row 4 is the one that matters most: confirm() treats a closed stdin as "take the default", so reusing it here would have made bootstrap.sh < /dev/null silently choose MIT — the original bug with extra steps.

scripts/check-license-marker.sh was tested on all four of its paths: drift → exit 1 with remediation; match → exit 0; NOTICE present → skip; docs/template/ absent → skip. The two skips matter — without them the check would fail in every adopted repo the moment phase 9 does its job.

Full dry run against this repository (the dogfooding case):

✓ 8. Ruleset … ! 9. Licence   - 10. De-template

Remaining MANUAL steps:
  [ ] ! LICENSE still carries the TEMPLATE author's MIT copyright…
  [ ] GitHub's API cannot create views…

LICENSE byte-identical afterwards. Running bootstrap against the template itself can never damage the upstream licence, because defer writes nothing.

Risk / rollback

RISK: L3 not run — the phase is exercised in scratch directories rather than a
real repo created from the template, because a full run needs gh auth against a
scratch repo. The nine-row matrix above covers every branch of the phase itself;
what it does not cover is phase 9 running in sequence after phases 0-8 on a
genuine template copy. Added to the release exit checklist.
Rollback: revert this commit. Purely additive; reverting restores the 9-phase
numbering and removes all licence handling, including NOTICE.

Checklist

  • Conventional Commit PR title
  • Linked issue using "Closes #N"
  • No secrets, no *.local.md files committed
  • Documentation updated where affected

The part to review first

Rewriting LICENSE's copyright line, on its own, is an MIT violation. Substantial portions of this template ship verbatim in every adopted repo — bootstrap.sh alone is now ~1,300 lines, plus the workflows, Makefile, 15 skills, issue forms — and MIT requires its copyright and permission notice to be included with them. Rewriting line 3 deletes the only copy of that notice from the repository.

So NOTICE is written on every answer that touches LICENSE, including the MIT one. Test rows 1 and 2 assert the template's copyright survives in both. This is the load-bearing part of the change, not the prompt.

Design decisions worth challenging

NOTICE, not a comment inside LICENSE. A LICENSE containing an all-rights-reserved notice and a verbatim MIT grant is genuinely ambiguous about what a client is receiving, and a client's counsel reads LICENSE and nothing else. Naming caveat stated rather than hidden: NOTICE carries Apache-2.0 §4(d) baggage; THIRD-PARTY-NOTICES is equally defensible and the choice is cosmetic.

No docs/template/LICENSE.proprietary.example file. Under --yes the phase writes nothing and files a manual step — and phase 10 then removes docs/template/, deleting the example in the very run that told the adopter to go read it. The body is a script constant (mirroring CHANGELOG_SEED) and is reproduced in docs/setup/licensing.md, which survives de-templating. licensing.md extracts both seeds from bootstrap.sh at authoring time so the doc cannot drift from what the script writes.

Phase 9 guards only LICENSE and NOTICE. Adding LICENSE to phase 10's guard list would make de-template skip itself on every run once phase 9 had written. The two guards are deliberately separate; grep -n "git status --porcelain --" shows them.

--license MODE is new surface. It is a mild concession against "convention over configuration", and I added it for one reason: without it neither write path can be exercised without a human at a TTY, and there is no test harness. Severable if you disagree — the phase works without it.

main "$@" is now BASH_SOURCE-guarded. Three lines. bash scripts/bootstrap.sh is unaffected (verified: --help still works). It exists so a single phase can be sourced and called in isolation, which is how the nine rows above were produced. This changes the script's entry contract, so flag it if you would rather not.

Not legal advice, and the docs say so

docs/setup/licensing.md (211 lines, of which ~75 are the two verbatim file bodies extracted from the script) ends with five questions it explicitly cannot answer and a lawyer should — including the one most likely to bite: whether your client contract warrants "no open-source components", which MIT-licensed scaffolding can breach even when correctly attributed. The proprietary body carries a self-removing "NOT LEGAL ADVICE / have counsel review, then delete this trailer" footer, and phase 9 emits the matching manual step.

I am not a lawyer. The mechanics (MIT requires notice retention; NOTICE is standard practice; relicensing a combined work is permitted) I am confident about. The contract-specific questions I am not, and the docs say so rather than guessing.

TzuH-Hsu and others added 3 commits September 2, 2026 12:05
…efaults

Each default contradicted something the repository already documents.

squash_merge_commit_title=PR_TITLE. AGENTS.md, CONTRIBUTING.md, pr-authoring
and branch-and-commit all state the PR title becomes the commit message on
main. GitHub's COMMIT_OR_PR_TITLE default makes that false whenever a PR has
exactly one commit -- visible in this repo's own log, where #7 and #9 carry
the (#N) suffix and #11, #13 and #15 do not.

squash_merge_commit_message=PR_BODY. The COMMIT_MESSAGES default concatenates
every branch commit message into the main commit body, and release-please
deliberately parses that body for further Conventional Commits and
BREAKING-CHANGE footers. Already live here: f98cbf9's body carries
"* chore: trigger CI on release PR", harmless only because chore is
release-please's hidden bucket. A branch commit reading "fix: wip" would have
produced a phantom changelog entry or an unintended bump.

allow_rebase_merge=false. The comment justifying rebase claimed release-please
merges its own PR. It does not -- ADR-0002, the release-please workflow header
and the release-management skill all require a human merge. Nor has rebase
ever been used: the history has no merge commits, all eight merged PRs were
squash-merged, and both release PRs cut their tags that way.

allow_update_branch=true. The ruleset sets
strict_required_status_checks_policy=false, so the "Update branch" button is
not offered at all without it.

Also corrects skills/release-management/SKILL.md, which instructed
`gh pr merge --merge`. allow_merge_commit=false has been set since day one, so
that command has always returned HTTP 405 -- the documented release procedure
was broken. Its parenthetical was wrong too: release-please does not merge.

Accepted cost: with PR_BODY an unmodified PR template lands verbatim in the
main commit message. BLANK was rejected -- it drops Co-authored-by trailers
and discards the RISK/rollback record. PR_BODY reduces the misparse surface
but does not eliminate it; a Conventional-Commit-shaped line in a PR body is
still parsed.

Closes #24

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bootstrap did nothing about security settings -- grep for secret, scanning,
visibility, vulnerability or dependabot in the script returned nothing. Two
consequences. Converting a repo private to public grants ACCESS to secret
scanning but does not enable it, and push protection in particular must be
switched on explicitly, which is how a public repo ends up without it. And
.github/dependabot.yml asserts Dependabot alerts and security updates are
enabled while nothing verifies that.

The write path is bounded by cost, not by capability: the only settings this
phase ever enables are free by construction.

- Public repo: offers secret scanning and push protection, both free.
- Private or internal: never writes them, under any flag. There they need a
  paid Advanced Security / Secret Protection seat, and a setup script must not
  commit an adopter's account to a per-committer charge. It reports the state
  and emits a MANUAL step.
- Dependabot alerts and automated security fixes: free everywhere, so offered
  regardless of visibility.
- Repository visibility is never changed and never offered. Private to public
  erases stars and watchers and publishes all Actions history -- a one-way
  door, and the phase comment says so, because "detect whether the repo is
  public" invites someone to add that prompt later.

Two states the GitHub UI blurs are kept distinct. Unreadable settings mean the
token lacks admin, not that the setting is off, so security_and_analysis
coming back null degrades to warn plus MANUAL rather than reporting "disabled".
And automated-security-fixes returns {"enabled":..,"paused":..}: enabled but
paused means no fix PR ever opens, so it is reported separately.

Verified read-only before writing the phase: gh api documents the
key[subkey]=value nested syntax, so the security_and_analysis PATCH stays a
normal run_or_dry call visible under --dry-run; /vulnerability-alerts returns
204 enabled and 404 disabled; /automated-security-fixes returns enabled and
paused.

Unlike phases 3/5/8 this phase runs several independent checks, so it
accumulates a result and calls record_phase once at the end -- the pattern
phase_issue_types uses, matching run_phase's one-record-per-exit-path
contract.

Phases 6/7/8 renumber to 7/8/9 across the script and docs. Because every read
runs for real under --dry-run, bootstrap --dry-run now doubles as a zero-risk
security audit.

Closes #26

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
De-templating rewrote README, CHANGELOG and the release-please manifest and
removed docs/template/, but never touched LICENSE -- grep for "license" in
scripts/ returned nothing. So every adopted repository shipped MIT, copyright
the template author. For an open-source adopter that is a wrong copyright
line. For commissioned work it is an irrevocable written grant to the whole
world of the right to use, modify, publish, distribute, sublicense and sell,
made before payment, which removes the leverage the payment clause was built
on. Four private client repos shipped that way.

The obvious fix is itself a defect. Substantial portions of this template ship
verbatim in every adopted repo, and MIT requires its copyright and permission
notice to travel with them; rewriting LICENSE's copyright line deletes the
only copy of that notice from the repository. So attribution is unconditional
-- every answer that writes LICENSE also writes NOTICE, MIT-keep included.
That, not the prompt, is the load-bearing part of this change.

Three answers, no default. A bare Enter re-asks and a closed stdin defers,
because confirm() treats closed stdin as "take the default", which here would
mean silently shipping the template author's MIT -- the original bug with
extra steps. --yes never writes a licence: it defers and files the decision as
the first manual step, rendered above the others with a ! marker via a new
MANUAL_URGENT list. --license mit|proprietary|defer answers non-interactively.

Phase 9 runs before de-template and is not gated by --keep-template-docs:
de-template returns early for three unrelated reasons, any of which would
otherwise swallow the decision. It guards only LICENSE and NOTICE; adding
LICENSE to phase 10's guard would make de-template skip itself on every run
once phase 9 had written. LICENSE is regenerated from a seed rather than
sed-patched, because holder names legitimately contain & and /.

No docs/template/LICENSE.proprietary.example: under --yes the phase writes
nothing and files a manual step, and phase 10 then removes docs/template --
deleting the example in the very run that told the adopter to read it. The
body lives as a script constant, mirroring CHANGELOG_SEED, and in
docs/setup/licensing.md which survives de-templating.

Also adds scripts/check-license-marker.sh, wired into make check. If
TEMPLATE_COPYRIGHT_* drifts from LICENSE the phase stops recognising its own
licence and silently does nothing -- the original defect with no symptom. The
check turns that into a red build, and no-ops in adopted repos.

main is now guarded by a BASH_SOURCE test so the file can be sourced for its
functions without running. Nothing in make verify covers this script; that
guard is the only unit-test surface it has, and all nine licence paths were
exercised through it.

De-template renumbers 9 to 10. ADR-0004 records the decision and seven
rejected alternatives.

Closes #28

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@TzuH-Hsu TzuH-Hsu added this to the v0.3.0 milestone Sep 2, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-05T13:03:33.620508Z fa1e9d2 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1ca709de87

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/bootstrap.sh Outdated
*) fail "unreachable licence choice '${LICENSE_CHOICE}'"; record_phase "9. Licence" "fail"; return 1 ;;
esac
rendered="${rendered//__YEAR__/$year}"
rendered="${rendered//__HOLDER__/$LICENSE_HOLDER}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Escape ampersands before substituting the holder

When the copyright holder contains &, Bash 5.2 with its default patsub_replacement setting expands that character to the matched text. I reproduced git config user.name='Smith & Jones a/s' producing Copyright (c) 2026 Smith __HOLDER__ Jones a/s while the phase reported the correct name and success. Avoid replacement-pattern expansion or escape replacement metacharacters before rendering the licence.

Useful? React with 👍 / 👎.

Comment thread scripts/bootstrap.sh
Comment on lines +1124 to +1127
if [ -f NOTICE ]; then
ok "NOTICE already exists — not overwriting"
return 0
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Append attribution to an existing NOTICE

When an adopter already has a clean, committed NOTICE for other dependencies, this early return treats mere file existence as proof that the template's MIT notice is present. The phase then replaces LICENSE, removing the only actual copy of the template notice while reporting success. Check for the specific upstream notice and append or merge it without discarding the adopter's existing content.

Useful? React with 👍 / 👎.

Comment thread docs/setup/licensing.md Outdated

MIT License

Copyright (c) __YEAR__ __HOLDER__

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Put the upstream identity in the manual NOTICE

For adopters following the documented manual path, copying this block produces the literal line Copyright (c) __YEAR__ __HOLDER__, even though the section claims to show what bootstrap writes and step 2 directs users to copy it. NOTICE must retain the template's original Copyright (c) 2026 TzuH-Hsu line, not placeholders or the adopter's identity, otherwise the manual fallback fails its attribution purpose.

Useful? React with 👍 / 👎.

Comment thread scripts/bootstrap.sh Outdated
fi
ok "LICENSE written (${LICENSE_CHOICE}, copyright ${year} ${LICENSE_HOLDER})"

write_notice || { record_phase "9. Licence" "fail"; return 1; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve LICENSE until NOTICE is safely written

When the existing LICENSE is writable but the directory cannot create NOTICE—for example because of directory permissions, quota, or an I/O failure—the script has already overwritten LICENSE before this call fails. The failure is merely recorded and bootstrap continues, leaving neither the original template notice nor the promised attribution file. Stage and verify NOTICE before replacing LICENSE, or roll back the licence write on failure.

Useful? React with 👍 / 👎.

Comment thread scripts/bootstrap.sh Outdated
Comment on lines +1470 to +1472
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
main "$@"
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep argument parsing out of sourced execution

When this file is sourced from a function or script that has positional arguments, the top-level parser runs before this guard and consumes the caller's arguments; an ordinary caller argument is treated as an unknown bootstrap option and invokes exit 1. I reproduced set -- unexpected; source scripts/bootstrap.sh terminating the shell, so the newly advertised source contract only works when the caller happens to have no arguments. Guard the parsing and other executable initialization as well as main.

Useful? React with 👍 / 👎.

TzuH-Hsu and others added 5 commits September 5, 2026 15:11
1. An ampersand in the copyright holder corrupted LICENSE on bash >= 5.2.
   ${var//pat/repl} expands an unescaped & in the REPLACEMENT to the matched
   text when patsub_replacement is on, which is the default from 5.2. A holder
   of "Smith & Jones a/s" rendered as "Smith __HOLDER__ Jones a/s" while the
   phase reported success. My earlier test missed it because macOS /bin/bash is
   3.2, where the option does not exist -- and escaping as \& is itself literal
   on 3.2, so no single expansion is correct on both. Replaced with subst_all,
   a literal prefix/suffix-removal helper that never interprets the
   replacement. Verified identical output on 3.2 and 5.3.

2. write_notice returned early whenever a NOTICE file existed, treating mere
   existence as proof the upstream notice was present. An adopter with their
   own NOTICE for other dependencies would have had LICENSE replaced while the
   only copy of the template's MIT notice was silently dropped -- the exact
   violation this phase exists to prevent. It now looks for the upstream
   copyright line, appends when absent, and keeps existing content.

3. docs/setup/licensing.md's NOTICE block shipped literal __YEAR__ __HOLDER__
   placeholders, so anyone following the documented manual path produced a
   NOTICE with no attribution in it. It now carries the template's real
   copyright line, with a sentence explaining that this one is deliberately not
   the adopter's identity.

Fixing (1) introduced a regression that the doc-vs-output diff then caught:
command substitution strips trailing newlines, so LICENSE lost its final
newline and the NOTICE closing rule ran into "SOFTWARE.". Both fixed, the
constraint is documented on subst_all, and the doc block is now byte-identical
to what the script writes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pushed the previous commit before reading the markdownlint result -- my
command chained on git add rather than gating on lint. CI would have caught
it; that is not the same as checking.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@TzuH-Hsu

TzuH-Hsu commented Sep 5, 2026

Copy link
Copy Markdown
Owner Author

Codex P1s addressed

1. & in the holder corrupted LICENSE on bash ≥5.2 — confirmed, and my original test was wrong.

bash 3.2 (macOS /bin/bash):  Copyright (c) 2026 Smith & Jones a/s      ← what I tested
bash 5.3 (brew):             Copyright (c) 2026 Smith __HOLDER__ Jones a/s

patsub_replacement is on by default from 5.2, and bootstrap.sh is #!/usr/bin/env bash, so this fires on Linux and on any machine with brew bash ahead on PATH. Escaping as \& is not a fix either — it stays literal on 3.2, so no single expansion is correct on both. Replaced with a subst_all prefix/suffix-removal helper. Verified byte-identical output on 3.2 and 5.3 for Smith & Jones a/s and plain names.

2. Existing NOTICE early-return. Correct and serious — an adopter with their own NOTICE would have had LICENSE replaced while the only copy of the upstream MIT notice was dropped. Now checks for the upstream copyright line specifically, appends when absent, keeps their content. Tested: a NOTICE naming libfoo retains it and gains the attribution; a second run adds no duplicate.

3. licensing.md placeholders. Correct. The manual path produced a NOTICE containing literal __YEAR__ __HOLDER__. It now carries the template's real line, plus a sentence saying that line is deliberately not the adopter's identity.

Fixing (1) introduced a regression that a doc-vs-output diff then caught: $(...) strips trailing newlines, so LICENSE lost its final newline and the NOTICE closing rule ran into SOFTWARE.. Both fixed; the constraint is now documented on subst_all; the doc block is byte-identical to what the script writes.

The two P2s on this PR (write-order, and the BASH_SOURCE guard not covering arg parsing) are not yet addressed.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0c2ed5024e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/bootstrap.sh
Comment on lines +1242 to +1245
if ! grep -qxF "$template_line" LICENSE; then
ok "LICENSE no longer carries the template's copyright line — leaving it alone"
record_phase "9. Licence" "skip"
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Create NOTICE when a custom LICENSE is already present

When an adopter commits a new LICENSE before first running bootstrap—for example, after manually replacing the inherited MIT file—this branch treats the licence setup as complete and exits without calling write_notice. The custom licence will commonly have removed the template's MIT permission notice, leaving no upstream attribution anywhere despite phase 9's stated purpose. Preserve the adopter's LICENSE, but still ensure NOTICE contains the template notice before returning.

Useful? React with 👍 / 👎.

Comment thread scripts/bootstrap.sh
Comment on lines +1164 to +1166
if grep -qxF "$upstream_line" NOTICE; then
ok "NOTICE already carries the ${TEMPLATE_NAME} attribution — leaving it alone"
return 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Verify the full MIT notice before treating NOTICE as complete

When an existing inventory-style NOTICE contains the upstream copyright line but not the MIT permission text, this check returns early and the subsequent LICENSE replacement removes the repository's only complete copy of the MIT notice. Fresh evidence beyond the earlier file-existence issue is that the new guard verifies only one copyright line, even though both the copyright and permission notices must be retained; check for a marker covering the complete attribution block before skipping.

Useful? React with 👍 / 👎.

Comment thread scripts/bootstrap.sh
Comment on lines +1294 to +1295
if [ "$KEEP_TEMPLATE_DOCS" -eq 1 ] && [ "$LICENSE_CHOICE" != "mit" ]; then
manual "README.md still shows the template's MIT badge and 'License: MIT' link (you kept it via --keep-template-docs) — update both to match your new LICENSE"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Warn about the MIT badge whenever de-templating does not finish

When proprietary is selected but phase 10 is subsequently declined, skipped because a de-template path is dirty, or fails, KEEP_TEMPLATE_DOCS remains zero and this condition emits no remediation step. The root README.md therefore retains its License: MIT badge while linking to the newly proprietary LICENSE, so the repository publicly advertises contradictory terms. Emit the warning whenever the old README survives, rather than only when --keep-template-docs was passed.

Useful? React with 👍 / 👎.

TzuH-Hsu and others added 3 commits September 5, 2026 15:31
1. An unreadable secret-scanning state was treated as disabled. security_and
   _analysis is only populated for callers with admin on the repo, so a token
   without it yields "public\tunknown\tunknown" -- a non-empty facts line that
   fell through to the else branch and prompted to enable, or PATCHed outright
   under --yes. That acts on a guess and reports a state never observed, which
   is the opposite of the unknown-is-not-disabled rule this phase was written
   around. There is now an explicit unknown branch that warns, emits a manual
   step, and writes nothing.

2. The Actions-permission troubleshooting entry still said phase 6 after the
   renumber moved it to 7, so the recovery text pointed at the Security prompt
   instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1. LICENSE was replaced before NOTICE was known to be writable. If the NOTICE
   write then failed -- directory permissions, quota, I/O -- the repository was
   left with neither the template's original notice nor the promised
   attribution, a worse state than not running the phase at all. NOTICE is now
   written first and LICENSE is only touched once it succeeded. Verified by
   making NOTICE an unwritable path: the phase fails and LICENSE is still the
   template MIT, byte for byte.

2. The BASH_SOURCE guard covered main but not the top-level argument parser, so
   sourcing the file from a shell that had positional parameters consumed the
   caller's arguments and exit 1'd on the first one it did not recognise --
   terminating the sourcing shell. Reproduced with `set -- unexpected-arg`.
   Both the parser and main now sit behind a bootstrap_is_main helper.
   Sourcing still applies set -euo pipefail to the caller; that is documented
   rather than silently changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@TzuH-Hsu

TzuH-Hsu commented Sep 5, 2026

Copy link
Copy Markdown
Owner Author

Codex P2s addressed

1. Write order. LICENSE was replaced before NOTICE was known to be writable, so a failed attribution write left the repo with neither the template's original notice nor the promised one — worse than not running the phase. NOTICE is now written first and LICENSE is only touched once it succeeded.

Verified by making NOTICE an unwritable path: the phase fails and LICENSE is still the template MIT, byte for byte.

2. Guard scope. Reproduced your case exactly:

set -- unexpected-arg; . ./scripts/bootstrap.sh   # printed usage, exited the shell

The guard covered main but not the top-level argument parser, which consumed the caller's positional parameters. Both now sit behind a bootstrap_is_main helper. After the fix the same command prints SURVIVED, args still: unexpected-arg, and executing normally still parses flags and rejects unknown ones.

One caveat left deliberately, and documented on the helper rather than silently changed: sourcing still applies set -euo pipefail to the caller. Source from a subshell if that matters.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c1938ddf39

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/bootstrap.sh
# Fixed-string whole-line match avoids regex-escaping "(c)". A LICENSE that
# no longer carries the template's line was already decided by the adopter.
local template_line="Copyright (c) ${TEMPLATE_COPYRIGHT_YEAR} ${TEMPLATE_COPYRIGHT_HOLDER}"
if ! grep -qxF "$template_line" LICENSE; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Confirm the whole template license before replacing it

When a clean, committed custom or composite LICENSE retains the upstream copyright line to satisfy MIT attribution, this single-line test misclassifies it as the untouched template license. A subsequent interactive choice or --license mit|proprietary then replaces the entire file, discarding the adopter's additional license terms; compare against the complete template license (or persist an explicit bootstrap marker) before allowing replacement.

Useful? React with 👍 / 👎.

set -euo pipefail
cd "$(dirname "$0")/.."

if [ ! -d "docs/template" ] || [ -f "NOTICE" ]; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not treat every NOTICE file as proof of adoption

When the template repository gains a NOTICE for an unrelated dependency, this condition permanently disables the marker check even though docs/template is still present. A later mismatch between LICENSE and TEMPLATE_COPYRIGHT_* therefore passes make check, defeating the red-build guarantee this script was added to provide; verify that the NOTICE is specifically bootstrap-generated or use a less ambiguous adoption signal.

Useful? React with 👍 / 👎.

@TzuH-Hsu
TzuH-Hsu changed the base branch from feat/26-security-phase to main September 5, 2026 12:53
# Conflicts:
#	docs/setup/bootstrap.md
#	scripts/bootstrap.sh
@TzuH-Hsu
TzuH-Hsu merged commit 9465360 into main Sep 5, 2026
1 check passed
@TzuH-Hsu
TzuH-Hsu deleted the feat/28-licence-phase branch September 5, 2026 13:00
@github-actions github-actions Bot mentioned this pull request Sep 5, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fa1e9d25ed

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +27 to +29
See `LICENSE` — chosen during bootstrap (phase 9). Third-party attribution,
including the MIT-licensed scaffolding this repository is built on, lives in
`NOTICE` and must be kept even if you relicense. Reasoning and both file

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Make the starter README truthful after licence deferral

When phase 9 is deferred—explicitly or through --yes—it leaves the inherited LICENSE untouched and does not create NOTICE, but phase 10 still installs this README claiming that the licence was chosen and the attribution lives in NOTICE. I reproduced the documented defer flow resulting in a missing NOTICE and both claims appearing in the final root README; make this wording conditional or tailor the generated README to the phase-9 outcome. skills/docs-hygiene/SKILL.mdL25-L25

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

De-templating never touches LICENSE, so every adopted repo ships the template author's MIT

1 participant