Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,13 +211,13 @@ checkout — answers ONE question about ONE repository: is it compliant with thi
shape, and what is missing. Run it before you propose a change to a repository
you did not scaffold, and again after any procedure above. It writes nothing
into the repository and fetches nothing, so there is no confirmation to get and
no `--dry-run` to do first. The checks are a REGISTRY with an empty `fix` slot
in each of them, which is where a repair mode will hang; nothing in it writes
today. The one exception to "writes nothing" is `--placement-plan <file>`, which
writes that file and nothing else — see 4. Installed, `openRepoShape --doctor`
takes `--json` and `--placement-plan <file>` as well, and KEEPS its temporary
clone of the standard, because the NEXT lines run from it: its last line names
that directory, which is the human's to delete once those commands have run.
no `--dry-run` to do first. The one exception to "writes nothing" is
`--placement-plan <file>`, which writes that file and nothing else — see 4.
The checks are a REGISTRY with an empty `fix` slot in each of them, which is
where a repair mode will hang; nothing in it writes today. Installed,
`openRepoShape --doctor` takes `--json` and `--placement-plan <file>` as well,
and KEEPS its temporary clone of the standard for the NEXT lines that name it:
its last line names that directory, the human's to delete once those have run.

1. **Read the verdict, then read the rows.** `COMPLIANT`, `COMPLIANT, SHAPE
BEHIND`, `DRIFTED`, `MISPLACED`, `INVALID`, `NOT A SHAPE ROOT` — exits 0, 1,
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1114,11 +1114,11 @@ openRepoShape --doctor # the repository you are standing in
./shape-doctor.py --root ../MedxEHR --json
```

It **writes nothing into the repository and fetches nothing from it**, and it
reimplements nothing: each row is a check this file documents. It compares
against the checkout it is RUN FROM, so the verdict is offline. Installed, it
clones the standard into a temporary directory and KEEPS it, because its NEXT
lines run from there: its last line names it, safe to delete after they run.
It **writes nothing into the repository but a `--placement-plan` file you name,
and fetches nothing from it**, and it reimplements nothing: each row is a check
this file documents. It compares against the checkout it is RUN FROM, so the
verdict is offline. Installed, it KEEPS its temporary clone of the standard for
the NEXT lines naming it: its last line names it, safe to delete after they run.

For an assembly root the rows are **naming** (this standard's own
`contracts/repository-naming.yaml`), **manifest** and **pins** (the project's
Expand Down
5 changes: 3 additions & 2 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ one today, and the generator refuses to write this file if one ever appears.
| [`scaffold-project.py`](#scaffold-projectpy) | Creates the three repositories and writes the pins. What the front door calls once the human has said yes. |
| [`adopt-project.py`](#adopt-projectpy) | Converts an EXISTING repository into the shape, in place, keeping its name and its history. |
| [`update-shape.py`](#update-shapepy) | Re-copies a project's shape files from this standard and re-pins them. It never merges. |
| [`shape-doctor.py`](#shape-doctorpy) | Answers ONE question about ONE repository: is it compliant with this shape, and what is missing. Writes nothing. |
| [`shape-doctor.py`](#shape-doctorpy) | Answers ONE question about ONE repository: is it compliant with this shape, and what is missing. Writes nothing but a `--placement-plan` file you name. |
| [`bootstrap`](#bootstrap) | Runs this standard's canonical bootstrap against a project that was never scaffolded, so a repository that elected nothing still has the command. |
| [`scripts/bump-leg.py`](#scriptsbump-legpy) | Advances ONE leg's pin — the gitlink, `contracts/<role>-pin.yaml` and every workflow `@<sha>` — in one lockstep commit. |
| [`scripts/family.py`](#scriptsfamilypy) | Creates a FAMILY holder and maintains its member pins. |
Expand Down Expand Up @@ -123,6 +123,7 @@ Anything after `--` is passed straight through to scaffold-project.py.
openRepoShape --install install (or update) this command into ~/.local/bin
openRepoShape --preflight check this MACHINE and stop; creates nothing
openRepoShape --doctor [<path>] [--json] [--placement-plan <file>]
diagnose a REPOSITORY against this standard
openRepoShape <Project> [--org <org>] [setup-project.py options] [-- <scaffold flags>]
openRepoShape --help | --version

Expand Down Expand Up @@ -168,7 +169,7 @@ ran this from, even inside the repository), and moves nothing.

Run beside a checkout of the standard, it uses that one. Run from anywhere
else, it clones the standard into a temporary directory first and KEEPS it,
because its NEXT lines name files in it. The report's last line names
for the NEXT lines that name files in it. The report's last line names
that directory, which is safe to delete once you are done with those
commands; under `--json` that line goes to stderr, so stdout stays one JSON
object.
Expand Down
17 changes: 9 additions & 8 deletions docs/handbook.html
Original file line number Diff line number Diff line change
Expand Up @@ -1451,7 +1451,7 @@ <h2>Bootstrap is COPIED into the project, not fetched</h2>

<section id="current">

<!-- reads README.md § Keeping a project's shape current: blob 9d21bfcf6a23fc23b4d2dd6a18a4f32f75ee400b -->
<!-- reads README.md § Keeping a project's shape current: blob 545639ebf5e9ab7837df5c012f216832dc7de56f -->

<p class="eyebrow">Reference</p>
<h2>Keeping a project&rsquo;s shape current</h2>
Expand Down Expand Up @@ -1513,7 +1513,7 @@ <h2>Keeping a project&rsquo;s shape current</h2>

<section id="doctor">

<!-- reads README.md § Keeping a project's shape current: blob 9d21bfcf6a23fc23b4d2dd6a18a4f32f75ee400b -->
<!-- reads README.md § Keeping a project's shape current: blob 545639ebf5e9ab7837df5c012f216832dc7de56f -->

<p class="eyebrow">Reference</p>
<h2>Is this repository compliant? <code>openRepoShape --doctor</code></h2>
Expand All @@ -1523,12 +1523,13 @@ <h2>Is this repository compliant? <code>openRepoShape --doctor</code></h2>
<div class="code" data-lang="sh"><pre><code>openRepoShape --doctor ../MedxEHR # or: ./shape-doctor.py --root ../MedxEHR
openRepoShape --doctor # the repository you are standing in
./shape-doctor.py --root ../MedxEHR --json</code></pre></div>
<p>It <strong>writes nothing into the repository and fetches nothing from it</strong>, and
it reimplements nothing. One flag writes at all, and it writes one file you named:
<code>--placement-plan</code>, below. It compares against the checkout it is RUN FROM, so
the verdict is offline. Installed, it clones the standard into a temporary directory and
KEEPS it, because its NEXT lines run from there: its last line names it, safe to delete
after they run.</p>
<p>It <strong>writes nothing into the repository but a <code>--placement-plan</code> file you
name, and fetches nothing from it</strong>, and it reimplements nothing. That flag, below, is
the one that writes at all, and it writes one file, at the path you give: a relative one
lands in the directory you stand in, inside the repository when you stand there. It
compares against the checkout it is RUN FROM, so the verdict is offline. Installed, it
clones the standard into a temporary directory and KEEPS it for the NEXT lines that name
it: its last line names it, safe to delete after they run.</p>
<p>For an assembly root the rows are <strong>naming</strong>, <strong>manifest</strong> and
<strong>pins</strong> (the project&rsquo;s OWN validators when it carries them &mdash; they
are the pinned copies, which is the point &mdash; else the template copies, and the row says
Expand Down
38 changes: 33 additions & 5 deletions openRepoShape
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,23 @@ say() { printf '%s\n' "$*"; }
set_mode() {
[ "$MODE" = "run" ] || [ "$MODE" = "$1" ] ||
die "--$MODE and --$1 are two different runs; pass one.
--install places this command, --doctor reads a repository and writes nothing."
--install places this command; --doctor reads a repository and writes
nothing into it but a --placement-plan <file> you name."
MODE="$1"
}
die() { printf '\nREFUSED: %s\n' "$1" >&2; exit "${2:-2}"; }

# THE DOCTOR'S DESCRIPTION SITS ON A LINE OF ITS OWN, IN THE SAME COLUMN
# (#546). Its full form outgrew that column when #500 added the two flags,
# and the description went with it; argparse puts help under an option too
# long for the column in just this way. The form itself stays one line, the
# one the refusal under `--doctor` prints.
usage() {
cat <<'USAGE'
openRepoShape --install install (or update) this command into ~/.local/bin
openRepoShape --preflight check this MACHINE and stop; creates nothing
openRepoShape --doctor [<path>] [--json] [--placement-plan <file>]
diagnose a REPOSITORY against this standard
openRepoShape <Project> [--org <org>] [setup-project.py options] [-- <scaffold flags>]
openRepoShape --help | --version

Expand Down Expand Up @@ -107,7 +114,7 @@ ran this from, even inside the repository), and moves nothing.

Run beside a checkout of the standard, it uses that one. Run from anywhere
else, it clones the standard into a temporary directory first and KEEPS it,
because its NEXT lines name files in it. The report's last line names
for the NEXT lines that name files in it. The report's last line names
that directory, which is safe to delete once you are done with those
commands; under `--json` that line goes to stderr, so stdout stays one JSON
object.
Expand Down Expand Up @@ -482,12 +489,19 @@ run_doctor() {
# 9808aec, SHOULD-1). Whether the clone is kept does not change, only
# what the line claims about it.
#
# AND "THAT NAMES IT", NEVER "RUNS FROM IT" (#546). Not every NEXT needs
# the clone: a `pins` row's, where the project carries its own validator,
# runs that copy and names no file in here. And "runs from it" read as
# "cd into it", where the `placement` row's relative `--placement-plan
# placement-plan.yaml` would write the plan inside the directory this
# same line calls safe to delete.
#
# IT NAMES THE DIRECTORY AND HANDS OUT NO `rm`. Every other line here
# that reads like a command is one to run; an agent told to run what the
# report names would delete the clone before the commands it holds ran.
if [ "$cloned" -eq 1 ]; then
trap - EXIT
kept="the standard this run used is kept at $WORKDIR, and any NEXT command above runs from it; it is safe to delete once you are done with them."
kept="the standard this run used is kept at $WORKDIR, for any NEXT command above that names it; it is safe to delete once you are done with them."
if [ "$DOCTOR_JSON" -eq 1 ]; then
say "$kept" >&2
else
Expand All @@ -513,9 +527,14 @@ while [ $# -gt 0 ]; do
# would otherwise be taken for the <Project> a scaffold run names. A
# word beginning with a dash is NOT the path -- it is a flag, and the
# two below are the only ones this mode takes.
#
# ONE PATH, HOWEVER MANY `--doctor`s (#546). A path after a second
# `--doctor` used to replace the first one silently; now it is left for
# the positional case below, which takes it for a second path, and the
# doctor refuses that as it refuses `--doctor . Atlas`.
--doctor)
set_mode doctor; shift
if [ $# -gt 0 ]; then
if [ $# -gt 0 ] && [ "$DOCTOR_PATH_GIVEN" -eq 0 ]; then
case "$1" in
-*) ;;
*) DOCTOR_PATH="$1"; DOCTOR_PATH_GIVEN=1; shift ;;
Expand All @@ -537,12 +556,19 @@ while [ $# -gt 0 ]; do
# The separate word is refused when it begins with `-`, as argparse
# refuses it, so `--placement-plan --json` is not a plan written to a
# file called `--json`; the `=` form is the way to name such a file.
#
# ONE PLAN PER RUN, AND A SECOND IS A REFUSAL, NEVER LAST-WINS (#546).
# Both used to reach `shape-doctor.py`, whose argparse kept the last, so
# `--placement-plan a.yaml --placement-plan b.yaml` wrote b.yaml alone
# and said nothing about a.yaml. The doctor writes one plan; a person who
# named two meant one of them, as with two modes or two paths above.
--json | --placement-plan | --placement-plan=*)
if [ "$MODE" != "doctor" ]; then
PASSTHROUGH+=("$1"); shift
elif [ "$1" = "--json" ]; then
DOCTOR_ARGS+=(--json); DOCTOR_JSON=1; shift
else
PLAN_BEFORE="$PLAN_FILE"
case "$1" in
--placement-plan=*) PLAN_FILE="${1#--placement-plan=}"; shift ;;
*)
Expand All @@ -555,6 +581,8 @@ while [ $# -gt 0 ]; do
esac
[ -n "$PLAN_FILE" ] ||
die "--placement-plan needs the file to write the plan to: --placement-plan <file>, or --placement-plan=<file> for a name that begins with '-'."
[ -z "$PLAN_BEFORE" ] ||
die "two placement plans, '$PLAN_BEFORE' and '$PLAN_FILE'. --doctor writes one: pass --placement-plan once."
DOCTOR_ARGS+=("--placement-plan=$PLAN_FILE")
fi
;;
Expand Down Expand Up @@ -602,7 +630,7 @@ if [ "$MODE" = "doctor" ]; then
# here too until #500, while the mode was one path wide; they are
# `shape-doctor.py`'s own, and are handed on above.
[ -z "$PROJECT" ] && [ -z "$ORG" ] && [ ${#PASSTHROUGH[@]} -eq 0 ] ||
die "--doctor takes a path, --json and --placement-plan <file>, and nothing else; it reads a repository and writes nothing into it.
die "--doctor takes a path, --json and --placement-plan <file>, and nothing else; it reads a repository and writes nothing into it but that <file>.
openRepoShape --doctor [<path>] [--json] [--placement-plan <file>]
To scaffold, drop --doctor. To check this machine, run: openRepoShape --preflight"
set +e
Expand Down
3 changes: 2 additions & 1 deletion scripts/render-cli-reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,8 @@ class Tool(NamedTuple):
"them. It never merges."),
Tool("shape-doctor.py", "python", "python3 shape-doctor.py",
"Answers ONE question about ONE repository: is it compliant with "
"this shape, and what is missing. Writes nothing."),
"this shape, and what is missing. Writes nothing but a "
"`--placement-plan` file you name."),
Tool("bootstrap", "python", "python3 bootstrap",
"Runs this standard's canonical bootstrap against a project that was "
"never scaffolded, so a repository that elected nothing still has "
Expand Down
32 changes: 16 additions & 16 deletions shape-doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,22 @@
`adopt-project.py plan` or `setup.sh`. This runs them in sequence, prints one
row each, and then prints ONE verdict. Every row is somebody else's check.

WHAT IT NEVER DOES. It never writes into the repository it is pointed at and
it never fetches. ONE FLAG WRITES AT ALL, and it writes one file the caller
named and nothing else: `--placement-plan <file>`, below. The upstream a
project's copies are compared against is THE CHECKOUT THIS FILE IS RUN FROM,
which is the whole of why the verdict is offline: no clone, no authenticated
call, and the same answer on a machine that has never spoken to github.com.
It runs the project's own validators OFFLINE: `run_validator` starts each
child with no token, no `gh` configuration and a proxy that goes nowhere
(#297), so they are offline whatever their own contract says. ONE ROW IS THE
EXCEPTION AND IT IS NAMED: `machine` asks `gh` who
it is logged in as (`setup-project.py --preflight` runs `gh --version`, `gh
auth status` and `gh api user`), which `gh` answers by talking to its host. That row
is `n/a` by status and cannot move the verdict -- a workstation missing `gh`
is not a repository being non-compliant -- so nothing this command CONCLUDES
depends on a network, and a machine with no `gh` at all is reported rather
than refused.
WHAT IT NEVER DOES. It writes nothing into the repository it is pointed at
but a `--placement-plan <file>` the caller names there, and it never fetches.
ONE FLAG WRITES AT ALL, and it writes one file the caller named and nothing
else: `--placement-plan <file>`, below. The upstream a project's copies are
compared against is THE CHECKOUT THIS FILE IS RUN FROM, which is the whole of
why the verdict is offline: no clone, no authenticated call, and the same
answer on a machine that has never spoken to github.com. It runs the project's
own validators OFFLINE: `run_validator` starts each child with no token, no
`gh` configuration and a proxy that goes nowhere (#297), so they are offline
whatever their own contract says. ONE ROW IS THE EXCEPTION AND IT IS NAMED:
`machine` asks `gh` who it is logged in as (`setup-project.py --preflight`
runs `gh --version`, `gh auth status` and `gh api user`), which `gh` answers
by talking to its host. That row is `n/a` by status and cannot move the
verdict -- a workstation missing `gh` is not a repository being non-compliant
-- so nothing this command CONCLUDES depends on a network, and a machine with
no `gh` at all is reported rather than refused.

THE CHECKS ARE A REGISTRY, not a function with eight paragraphs in it. Each
one is a `Check` -- an `id`, the root kinds it `applies_to`, a `run(ctx)` that
Expand Down
19 changes: 19 additions & 0 deletions tests/test_openreposhape_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,21 @@ def test_help_prints_every_usage_line():
assert line in lines, f"--help never printed:\n {line}"


def test_the_doctor_line_keeps_its_description_column():
"""#500 gave the doctor's synopsis its two flags, and the full form
filled the column the other lines describe themselves in, so its
description went (#546). It is back, on the line under the form and in
that same column, where argparse puts the help of an option too long for
it; the form itself stays one line."""
lines = run_cmd("--help").stdout.splitlines()
column = USAGE_LINES[0].index("install (or update)")
assert USAGE_LINES[1].index("check this MACHINE") == column
doctor = lines.index(next(line for line in USAGE_LINES if "--doctor" in line))
assert lines[doctor + 1] == \
" " * column + "diagnose a REPOSITORY against this standard", (
lines[doctor:doctor + 2])


def test_help_points_at_openrepotools():
"""`--help` used to list the other two commands; it names their new home.

Expand Down Expand Up @@ -591,6 +606,10 @@ def test_two_modes_is_a_refusal_not_last_wins(args, tmp_path):
env={"OPENREPOSHAPE_BIN_DIR": str(bin_dir)})
assert result.returncode == 2, result.stdout + result.stderr
assert "two different runs" in result.stderr
# "and writes nothing" was false of a `--placement-plan` typed inside the
# repository (#546): it says which one file the doctor does write.
assert "--doctor reads a repository and writes nothing into it but a " \
"--placement-plan <file> you name." in " ".join(result.stderr.split())
assert not bin_dir.exists(), "a refused run placed a command anyway"


Expand Down
Loading
Loading