Skip to content

feature/Scaffold-skill · L-260906-8ac105 — front door to a project not yet made - #21

Merged
lchoquel merged 25 commits into
devfrom
feature/Scaffold-skill
Sep 13, 2026
Merged

lchoquel merged 25 commits into
devfrom
feature/Scaffold-skill

Conversation

@lchoquel

@lchoquel lchoquel commented Sep 13, 2026

Copy link
Copy Markdown
Member

Adds pipelex-scaffold, the plugin's front door for a user who has a method but no project: it acquires one of the two Pipelex starters and drives that clone's own bootstrap skill, or runs the ecosystem's initializer when the user wants their own framework, and hands the result to /pipelex-integrate. The work was cut out of #18 so the integrate half could land alone; this branch takes dev as a merge rather than a rebase, which keeps the recorded review passes reachable, and the integrate skill's two forward references to /pipelex-scaffold are restored now that the skill ships.

It also implements the founder's ruling on L-260912-724b71: a directory holding nothing but .git reads as empty and is written into, while everything else goes on refusing. The cruft list was declined in that ruling, so the exception is written as one directory entry named .git rather than a predicate over ignorable files, and the test pins .DS_Store, .idea/, .vscode/ and Thumbs.db as still refusing.

Closes L-260906-8ac105

🤖 Generated with Claude Code


Summary by cubic

Adds pipelex-scaffold, the front door for a user who has a method but no project: it clones one of the two Pipelex starters and runs that clone's own bootstrap skill, or runs the ecosystem's initializer when the user wants their own framework, then hands the result to /pipelex-integrate. Closes L-260906-8ac105 and restores /pipelex-integrate's two forward references to the skill.

New Features

  • Ships in all three plugin targets with no MCP dependency, plus references/starters.md and references/initializers.md.

Bug Fixes

  • Implements the L-260912-724b71 ruling: a directory holding only .git reads as empty; any other entry refuses, with .DS_Store, .idea/, .vscode/, and Thumbs.db pinned as refusing.
  • Branch A honors that state by cloning into a temp path beside the target and copying the starter's contents in, preserving the user's repository; the destination is resolved first so . and paths with spaces work, and a test executes the recipe against real directory states.
  • pipelex-integrate now offers /pipelex-scaffold when no project exists instead of stopping.
  • Scaffold recipes and references now agree: uv init uses --no-workspace, key writes never duplicate an existing value, the pristine commit scopes staging and commit, says what it will stage, and asks, starter clone blocks name one starter, gh repo create --clone handles its actual destination, and the minimal TypeScript recipe skips mkdir <dir>.
  • The initializer branch locks the Python project before the hand-off, since uv init writes no lock file and pipelex-integrate reads an absent one as pip.
  • The fresh-clone shortcut and template-checkout stop no longer share a detection signal; the discipline test covers the template and all three renders.

Written for commit 96bb083. Summary will update on new commits.

Review in cubic

lchoquel and others added 23 commits September 6, 2026 22:45
Brings in pipelex-synthetic-inputs, the plugin's file factory (#13), under
this branch's design/edit work.

The only conflict was CHANGELOG.md, where both sides had inserted a bullet
at the top of the Unreleased "Changed" section, above the shared ruff pin.
The two entries make disjoint claims — one about pipelex-design becoming
model-invocable, the other about pipelex-inputs delegating file generation
— so both are kept verbatim.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DYrbgEm55V4QP3uJmgTqRS
…ills

`pipelex-integrate` wires an MTHDS method into an existing TypeScript or
Python codebase: it reads the method's signature from the validate verdict's
`main_pipe`, adds the formatter and linter exclusions before anything is
generated, drives `mthds_codegen`'s write arm so no artifact byte passes
through the model, writes a `sources.json` sidecar, an offline drift gate and
a typed call site, and defers wholesale to a project that already owns a
codegen harness. `pipelex-scaffold` is the front door to a new project: a
starter through its own bootstrap skill, or the ecosystem's own initializer,
and it is MCP-free.

Both ship `references/` — the second and third skills to do so — including
`codegen-check.mjs`, the offline gate copied into TypeScript projects.
`wire-output.ts` was struck before it was written: the ts-zod `.nullish()`
emitter fix shipped in pipelex v0.56.0, which makes the helper lossy rather
than protective.

The rest of the family gained one-line wiring: `pipelex-edit` and
`pipelex-design` announce a stale sidecar and offer the refresh,
`pipelex-design`'s delivery forks between the two new skills on whether the
workspace holds a project manifest, and `pipelex-inputs` hands off at its
closing report.

Advances L-260830-344594
Advances L-260906-8ac105

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DYrbgEm55V4QP3uJmgTqRS
Seven Phase 3 scenarios ran as cold headless sessions against the local
workshop — a fresh agent per scenario, given a user's own phrasing and no
knowledge of the campaign. Three findings survived.

The refresh-mode rule read the wrong value. `crate_fingerprint` covers the
whole bundle, not its concept set, so a prompt-only edit moves it while the
projected code is byte-identical — every artifact's single stamp line moves
with it and nothing else does. The honest signal is the lock's
`artifacts[].content_hash`, which is computed beneath the stamp; both the
skill and the design's §4.10 now read that instead.

The skill and its Python reference disagreed about where a bundle lives:
Step 1 said an in-project bundle never moves, while the reference's packaged
row put it inside the import package. The reference is right, and a wheel
build proved it rather than assuming it — a root-level `methods/` does not
ship. Step 1 now names the exception and asks for `git mv`.

The family's staleness notice searched beside the bundle and reported a clean
bill, while the sidecar sat beside the generated tree where the skill puts
it. The location had been written as background prose rather than as a search
instruction; `pipelex-edit` and `pipelex-design` now say to search the whole
project and give the grep. Re-run, the notice fired correctly.

Advances L-260830-344594

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DYrbgEm55V4QP3uJmgTqRS
Two rulings from Louis, both recorded in the tracker rather than acted on in
the templates.

This work stays on api-dev.pipelex.com until he says otherwise: the key on
this machine is dev-scoped and updating the prod environment is off his list
for a while. Phase 4's live-run step said "live" in a way that read as
production; it now says the published workshop against the dev API, and
notes that a prod 403 is the expected state rather than a defect to chase.

The Python two-audience rule stands as ratified. Asked why a host carrying a
@pipe_func gets python-structures at all, the answer is that the runtime
enforces it — func_registry.py:372 refuses a pipe func whose return type is
not a StuffContent subclass — so the target is what makes a pipe func
writable. Rather than re-cut the rule, Louis asked for the requirement to be
relaxed at its source. That is L-260907-3ea0c0 against pipelex, and the
target question is parked on it.

Advances L-260830-344594

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DYrbgEm55V4QP3uJmgTqRS
dev brought pipelex-plugins#16: the Vibe target now ships the workshop launcher as mcp/vibe-mcp.toml, and the MCP-backed skills' Vibe stop message points at that fragment. Conflicts in CHANGELOG.md, CLAUDE.md and README.md kept both sides: dev's Vibe fragment text, with pipelex-integrate and mthds_codegen re-added to the MCP-backed skill and tool lists.

The merge also resolves one semantic conflict. pipelex-integrate is in MCP_SKILLS, so its Vibe stop message and auth line now carry dev's fragment wording, which the stricter test_absent_tools_stop_message_matches_platform requires. The renders were regenerated with make build, not hand-merged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
pipelex-mcp dev settles the entry pipe from the report's default_pipe_ref, which follows a package's METHODS.toml, and omits main_pipe whole in three cases: the method settles no entry pipe, the entry pipe's contract did not come back whole, or the workshop predates the signature. The skill named only the first and the third, and told a by-ref or by-id source to blame an old workshop. Step 3 now names all three, says the call site is typed and run against main_pipe.pipe_ref (which can differ from the bundle's own main_pipe for a published package), and the stop for a by-ref or by-id source no longer asserts which cause applies.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ethod

Two dogfood findings on the integrate skill. First, every method of a target emits the same file names, so generating a second method into a directory that already holds another method's tree overwrites it outright and reports no orphan. The template only guarded a lock whose sidecar names a different method, while design section 4.7 says any lock not named by this method's sidecar; step 4 now carries that rule, including a lock with no sidecar at all and a directory the user named. Second, faced with a hand-written file at an artifact path, the skill offered to delete it. It now says never to offer that, and applies the rule when the file is noticed before the tool is called.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Asked to scaffold into a directory holding a file of the user's, the skill refused to write into it and then offered to move the file aside and merge it back afterwards. The refusal is the whole rule (scaffold-design.md section 7): the answer to an occupied directory is another directory, never making room in this one. The branch table and the failure table now say so, and a test pins it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dogfooded on a PATH with no node, the skill found the machine's nvm, activated it and carried on cloning and installing. That is the useful answer and it installs nothing, but the skill's prerequisites read as if PATH were the whole test, so the behaviour was outside what it says. It now distinguishes a runtime the machine lacks from one only the PATH is missing: check nvm, fnm, volta, asdf or mise, use what they already hold, say which one was used and that the user's own shell may not have it, and stop only when no runtime can be reached that way. Installing a toolchain is still never done.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two dogfood runs on cold TypeScript projects failed their own type check inside the generated tree: the ts-zod emitter writes binder.ts's import as `from "./types"`, which a plain Node ESM project (type module with moduleResolution nodenext or node16) rejects, and so does Node at runtime. Both runs diagnosed it from scratch and refused to patch the stamped file, which is the right posture but not something each user should have to rediscover. The failure table and the TypeScript reference now name the defect, say the fix is upstream in the emitter, and say that moving the project to a bundler resolution is the user's decision, not the skill's. Filed against pipelex as L-260912-857a5a.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dogfooded with the harness launched in a sibling directory, the skill never met the containment error the rule is written around: it passed an output_dir relative to the workshop's working directory, the write landed legally beside the wrong project, and the run then moved the tree into place and carried on. The bytes survived that, but the project was left with a refresh that hits the same mismatch every time and a sidecar whose project-relative paths name a project the workshop cannot see. Step 6 now says to read the path from the workshop's working directory to the generated directory before calling, treat a climbing path or a project root elsewhere on disk as the stop, and never write beside the wrong project to move the tree over afterwards.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The two designs carry the amendments the Phase 3 runs forced — the three reasons a verdict has no main_pipe and the entry pipe following a package manifest, containment read before the first write, an occupied generated directory refused however it was named, a non-empty target never cleared, and a runtime behind a version manager — and docs/decisions.md gains a dated entry for the same set so a reader of the repo finds them without opening the campaign. The changelog's integrate entry now names the two silent failures the skill refuses and the emitter defect it reports rather than patches.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A dogfood run checking whether PIPELEX_API_KEY was set printed the raw value into a tool call's output, and then told the user to consider rotating it. The skill said never to print a key but never said how to look for one, which leaves the obvious `env | grep PIPELEX` as the path of least resistance. It now gives the presence test that reveals nothing, and says that echoing the value — in a diagnostic, a message or a command substitution — makes the key one to rotate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every remaining scenario is recorded with its verdict and its evidence: the six that changed a template and the commit each fix landed in, the two that only surfaced the upstream emitter defect, and the two that cannot be run here — SC-9 needs the founder's say-so for a GitHub repository, and SC-10's empty-key branch is unreachable on a machine whose shell profile exports a key into every tool shell. Checkpoint 2 records the workshop and starter SHAs the runs used. Phase 4 gains the upstream re-check, the TS-1 half of its live run against the published workshop, and the fact that the cut carrying main_pipe is still unpublished, which is what this pull request's merge waits on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dev removed the vestigial gha-tests Makefile target (pipelex-plugins#17) while this branch was being dogfooded. No conflict: nothing here touches the Makefile.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The dogfood matrix exercised the skills in real sessions; nothing ever compiled or ran the code they write into a user's project, and that is where almost every defect was.

The TypeScript call site declared its inputs as an `interface`. TypeScript gives a type alias of an object type an implicit index signature and an interface none, so it was not assignable to the SDK's `inputs: Record<string, unknown>` and failed TS2322 on every resolution, bundler included — the first thing `tsc --noEmit` would have said. Both call sites read only `main.mthds` while generation takes every file of the bundle, so a multi-file method got correct types over a run that could not load what they were projected from; a bundle is one closure, and both now submit all of it. The module's own relative imports are extensionless, which is right only on a bundler resolution, so they are named as the agent's to extend with `.js` — not as the emitter defect, which is about the stamped tree and was the row they used to land on.

The scaffold's `uv add` recipes ran wherever the agent stood rather than in the project `uv init` had just made: from a parent that is the user's own pyproject.toml and lockfile, and from nowhere it simply fails. The Django row already used the subshell the other two needed. The minimal TypeScript recipe reaches its commit with a populated `node_modules/` and no `.gitignore` from either `npm init -y` or `tsc --init`, and its `--module nodenext` with `"type": "module"` is exactly the shape the emitter defect breaks, which the reference now says where the recipe is chosen.

The offline gate said `current` on two inputs it should not have. A sidecar whose `sources` was present but not an object was coerced to `{}`: nothing checked, nothing printed, exit 0 — the one input both silent and green, with an array the shape to expect since `method.files` beside it really is one. `null` is why the fix does not use `??`. And the decoder stripped a leading BOM before hashing, so a BOM'd artifact matched the lock while the bytes on disk were hand-edited. Both fail closed now, the absent and empty cases announce themselves, and `process.exitCode` replaces `process.exit` so the drift lines survive a pipe.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Each of the three integrate guards the dogfood added refuses a case it is itself responsible for.

The occupied-directory rule reads a `codegen.lock` with no sidecar as another generation's. But the lock is written in step 6 and the sidecar in step 7, so every stop between them — a reported `is_current: false`, a non-empty `orphans[]`, a partial write whose retry also failed — leaves exactly that state for the method being integrated right now, and the rule then sends the agent to a different directory: a stranded half-tree plus a second tree for one method, which is the fragmentation the rule exists to prevent. A harness-owned layout is the same shape for a different reason, keeping no sidecar by design, so the rule forbade the write the harness section prescribes two screens later. Both are named exceptions now, with regeneration in place as the answer.

The containment pre-check was a lexical reading, which is wrong in both directions: a symlink inside the workshop pointing at a project outside it reads as contained, and on macOS a project under `/tmp` reads as outside because the workshop's own `process.cwd()` is the `/private` form of the same place. Both sides are resolved before comparing. The reading is also taken as soon as step 1 names the project, because both inputs are known there and by the old placement a bundle copy, possibly a staged `git mv`, and the step-5 exclusions are already on disk — so a stop that happens anyway now says what is already written, none of it the agent's to revert.

Two more from the same family. `pipe_ref` is namespaced and the run route takes the bare code, and nothing said to strip the domain — a confusion that type-checks, passes the offline gate, and fails only on a real run, which step 11 never does. A `variable` or `fixed` output arrives as an array that a single-concept parser rejects, so narrowing follows the multiplicity step 3 recorded. And the three causes of an absent `main_pipe` are all three in the failure table, where they matter most because the remedy differs by cause: a contract that did not come back whole comes from the runner, and no workshop refresh touches it. The same signature rides the verdict's text summary, so a host that does not surface structured content is not a method without a signature.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…edges

`8356f2c` fixed looking a key up and left putting one down. It sanctioned "an in-place edit", which for a file-editing tool means passing the literal value as a parameter — and a tool call's parameters are the transcript, so that is the one form of write that cannot be made safe. The value now moves only through a shell that expands the variable itself. Reading the env file back afterwards is refused by name, because that is the reflex after writing and the first move when a later step fails, and the rule had enumerated shell constructions only. Confirming the write uses a file-side presence test that reveals nothing, and the report says the value was taken from the environment and not validated — a placeholder passes `-n` and fails the first run, and this skill never calls the API, so it cannot tell. The row carrying this guidance had an unescaped `|` inside a code span and rendered as four broken cells.

Reaching a runtime behind a version manager means resolving it to a path and carrying that into every later command. Shell state does not survive a command here, and the profile that would have had the runtime is the one that did not — so sourcing `nvm.sh` bought nothing, the prerequisite read as met, the pristine commit was spent, and the delegated bootstrap failed on `node: command not found`, which is worse than the honest stop. Three things the clause does not license: a shim that resolves and then answers nothing is not a runtime, the starter's floor still applies to a version a manager happens to hold, and `volta` and `mise` install a version they lack, which is the toolchain install this step forbids. The workshop is spawned on the harness's own `PATH`, so on that machine the hand-off needs a relaunch — the report used to promise the opposite unconditionally.

Three destructive edges. The `rm -rf <dir>/.git` was not chained to the clone that creates `<dir>`, so on a path that already held a repository it destroyed the user's history. The `.env` copy had no `-n`, and the fresh-clone shortcut enters that step in a directory the user was already working in, where it would overwrite a key they had filled. And the non-empty refusal read as absolute while the fresh-clone shortcut six lines below enters branch A in a directory non-empty by definition; it is scoped to a directory you are creating a project in, which removes the contradiction without changing what it refuses — whether `.git`-only and `.DS_Store`-only should still be refused is a judgment call, filed rather than taken.

Also: `npm create next-app` without the `--` separator, so npm ate the flags and the advertised non-interactive command prompted; a `.gitignore` read before staging, since `npm init -y` writes none; an initializer that commits as well as `git init`s has already made the pristine commit, as `create-next-app` does; and the decisions log dated the model-invocable change to a day with no session behind it, against the commit that removed the flag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… met there

Round 1 compiled the TypeScript snippet; this round ran the initializers and
then integrated into the project they actually produce, which is where the two
skills meet and where every defect below lived.

`tsc --init` writes `"types": []` as an active key, switching off the
`@types/node` the recipe installs the line before, so the call site failed
TS2591 on `node:fs/promises`, `node:path` and `process`. Every `uv init` now
passes `--no-workspace`: inside a directory that already holds a
`pyproject.toml`, a bare one appends `[tool.uv.workspace]` to the user's own
file and leaves the lock at the parent root. The Vite and Express follow-on
`npm install` lines are parenthesised for the same reason the `uv add` lines
were, and npm is worse — it finds the parent and exits 0. Branch B's pristine
commit could reach an enclosing repository, because `git -C <dir>` scopes
nothing and `uv init` only `git init`s a standalone project; the step now tests
`rev-parse --show-toplevel`, both commits carry `-- .`, and the read-back names
paths. The clone guard was missing from `references/starters.md`, the file the
skill names as carrying every command. The offline gate still reported
`current` for a sidecar that was valid JSON and not an object, because
`sidecar?.sources` does to the sidecar what `??` would have done to `sources`.

Also: the helper budget said two and listed one, licensing the wire-output
helper the campaign struck; and the reference-copy test compared a fresh
`copytree` against its own source, so it could not see a stale committed copy.

Each fix is pinned by a test that failed before it, the gate's by one that runs
the script rather than reading it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Takes dev's landed pipelex-integrate (#18) and orphan ruling (#20) into the
scaffold branch as a merge rather than a rebase, so the review passes recorded
against this branch's SHAs stay reachable. Every conflict was in a file the
removal commit e449b91 had narrowed to what shipped without the scaffold; dev's
later text is the base and the scaffold halves are restored into it, including
pipelex-integrate's two forward references to the skill.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
L-260912-724b71, ruled 2026-09-13. The non-empty refusal treated any existing
entry as occupancy, so the skill refused a state it produces itself: `mkdir
my-app && cd my-app && git init` is an ordinary way to arrive, and branch B runs
`git init -b main` in the directory it is working in one step later.

The cruft list was declined in the same ruling, so the exception is written as
one directory entry named `.git` and never as a predicate over ignorable files
that a later reader could extend without a decision. Every other entry still
refuses, and the test pins the declined names as refusing so that admitting one
means rewording an asserted sentence. Never offering to clear anything is
untouched: narrowing what counts as occupied is not permission to empty what is.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review round 1 read the skill as a document to execute rather than to
proofread, and found the places where the one line an agent runs
disagreed with the reference it is told to read.

The named-framework recipe still prescribed a bare `uv init --package
<dir>`, so it appended a `[tool.uv.workspace]` table to the user's own
pyproject.toml -- the exact hazard the reference documents in bold as
fixed everywhere. The key write was an unconditional append, which on
the fresh-clone shortcut landed a second PIPELEX_API_KEY after one the
user had filled; dotenv resolves a repeated name to the later line, so a
stale exported value replaced a working key while the report claimed
theirs was kept. The pristine commit carried its `-- .` pathspec on the
staging but not on the commit, and a bare `git commit` commits the whole
index, so a user's work staged elsewhere in an enclosing repository rode
along under this skill's message. `gh repo create --clone` takes no
destination and clones into ./<name>, which every later step addressed
as <dir>. The minimal TypeScript recipe opened with `mkdir <dir>`, which
aborts its own chain in the "here" directory the skill permits, and its
default resolution is the one the same file calls broken. The
fresh-clone shortcut and the template-checkout stop shared a detection
signal and prescribed opposite actions.

The discipline test pinned the --no-workspace rule in the reference and
never in the skill body, which is how the half-application shipped; it
now runs on the template and all three renders, and was shown to fail
when either guard is struck.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@lchoquel

Copy link
Copy Markdown
Member Author

Review round 1 — bar open — reviewed d53f3cf, now at 51395ef

Reviewers that produced a review: cubic, Codex review (gpt-6-astra, high effort), Codex adversarial (gpt-6-astra, medium, verdict needs-attention / "do not ship yet"), and the official code-review. The round was raised from profile 3 to 4 mid-flight, so the parameters are mixed: the Codex review pass ran at 3's settings and the adversarial pass at 4's. code-review was invoked at medium but announced minimal, so its coverage is shallower than the profile asked for — recorded rather than smoothed over.

Fixed in 51395ef, each verified in the tree before it was touched:

  • The named-framework recipe prescribed a bare uv init --package <dir> while references/initializers.md states in bold that --no-workspace is on every uv init — so the executable line appended a [tool.uv.workspace] table to the user's own pyproject.toml. Raised independently by three reviewers. The discipline test pinned the rule in the reference and never in the skill body, which is how the half-application shipped; it now runs on the template and all three renders.
  • The key write was an unconditional append. On the fresh-clone shortcut it landed a second PIPELEX_API_KEY after one the user had filled, and every dotenv reader resolves a repeated name to the later line — a stale exported value silently replacing a working key while the report said theirs was kept. Now gated on a file-side presence test.
  • git -C <dir> add -A -- . bounds the staging, but the bare git commit after it commits the whole index. Demonstrated: a file staged elsewhere in an enclosing repository was committed under this skill's message. Both commits carry the pathspec now, and the guard's description says what it actually guarantees.
  • gh repo create --clone takes no destination and clones into ./<name>, which every later step addressed as <dir>.
  • The minimal TypeScript recipe opened with mkdir <dir>, which aborts its own && chain when <dir> is the "here" directory the skill permits (mkdir: .: File exists).
  • The fresh-clone shortcut and the template-checkout stop shared a detection signal and prescribed opposite actions.
  • The default TypeScript row prescribed the resolution the same file calls broken; the caveat is now where the command is chosen.

Deferred with traces:

  • A directory holding only .git — which the 2026-09-13 ruling admits — is one branch A cannot clone into: git clone refuses it, while branch B's uv init succeeds on the identical directory. Every fix changes behaviour that was ruled on, so it is a decision item rather than an edit.
  • A branch-B .env that nothing loads was rediscovered; it was already open and needed no new trace.
  • The remaining confirmed-but-minor findings are recorded in wip/pipelex-integrate/scaffold-review-deferrals.md.

make agent-check and make agent-test both pass at the tip. No version bump: version-check.yml fires only on pull requests into main or release/*, and this repo accumulates under [Unreleased] with /release moving targets/prod.toml.

lchoquel and others added 2 commits September 13, 2026 15:32
A directory whose only entry is `.git` reads as empty, which the initializer
branch can honour and the starter branch could not: its first command on the
chosen directory is `git clone`, and git refuses a destination that already
holds a `.git`. The skill accepted a directory it then could not populate.
Branch A now clones into a mktemp path beside the target, discards the
template's history there before anything moves, re-reads the directory
immediately before the copy, and carries the contents in with
`cp -R "$tmp"/. <dir>/` so that the entries beginning with a dot come too —
reaching the end state the default recipe reaches, with the user's repository,
branch, history and remote left standing.

The recipe is executed rather than read. A new test extracts it from the skill
and runs it against a directory that does not exist, an empty one, one holding
a real repository with commits on a named branch, one holding `.git` beside a
file of the user's, and one holding only the cruft the earlier ruling declined
to exempt — reading back the surviving commits, branch and reflog, and logging
every path any `rm` is pointed at to prove none lies under the chosen directory.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…stated in one copy

Review round 2 read round 1's own fixes and the founder's ruling together.

The preserving acquisition derived its temporary path from the destination's
spelling. `dirname .` is `.`, so a destination given as `.` — the ordinary one,
since the user is standing in the directory they just `git init`-ed — put the
temporary clone inside the destination, where the emptiness re-check found it
beside `.git` and refused. The acquisition served every shape except the one the
ruling exists for, and every recipe test bound the destination to an absolute
path, which is why it took an execution test spelled `.` to show it. The
destination is resolved first now, which also gives the chain a variable to
quote, so a directory whose name holds a space reaches `cp` whole.

The references carried both starters' clone URLs on consecutive lines of a block
whose own prose calls it one chain: run as written, the second clone fails on the
destination the first just filled and its handler deletes the successful clone
before anything is copied. The blocks name one starter now.

Three rules held in one copy and were executed from another. Round 1 put the
`-- .` pathspec on the pristine commit as well as its staging and wrote that it
is on both commands for a reason, while the reference kept the bare commit — the
half-application that round was convened to fix, mirrored. The initializers
reference prescribed the inference the skill body forbids by name in favour of
testing `rev-parse --show-toplevel`. And the lone-`.git` rule justified itself
with a `git init` that must not run in precisely that case.

The confirmation rule was reconciled with the ruling it predates: it exempted the
pristine commit as landing on a directory this skill just created, which the
preserving acquisition makes false on all three grounds, so that commit now says
what it will stage and asks. The initializer branch locks its Python project
before the hand-off, since `uv init` writes neither a lock file nor an
environment and `pipelex-integrate` reads an absent lock file as pip.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@lchoquel

Copy link
Copy Markdown
Member Author

Review round 2 — bar defects — reviewed 3a57f7d, now at 96bb083

Reviewers that produced a review: cubic, Codex review and Codex adversarial (both gpt-6-astra at medium, profile 4's mode = "both"), and the official code-review. Note for the record that code-review announced no effort level at all, despite medium being passed explicitly — so the depth it actually ran at is not confirmable from its own output.

Seven findings fixed. The sharpest was found independently by three of us: the preserving acquisition derived its temporary path from the destination's spelling, and dirname . is . — so a destination given as ., which is the ordinary one since the user is standing in the directory they just git init-ed, put the temporary clone inside the target, where the emptiness re-check found it beside .git and refused. The acquisition served every shape except the one the ruling exists for. It was invisible because every recipe test bound the destination to an absolute path; there is now an execution test spelled . and ./, proven to fail against the old form.

Where the rest sit is the decision signal. In the ruling's implementation (3a57f7d): that one, plus the references carrying both starters' clone URLs on consecutive lines of a block whose own prose calls it one chain. In round 1's own fixes (51395ef): its -- . pathspec rule reached the skill body and not the reference, while its changelog line said both commits carried it — the same half-application round 1 was convened to fix, mirrored. In the original work: the initializers reference prescribing the inference the body forbids by name, the lone-.git rule justifying itself with a git init that must not run in that case, the Mode section's confirmation carve-out that the ruling made false on all three grounds, and branch B handing over a Python project with neither lock file nor environment.

Five findings deferred with traces in wip/pipelex-integrate/scaffold-review-deferrals.md, including the .git-as-a-file case, which was weighed deliberately and left: branch B accepts the identical directory through rev-parse --show-toplevel, so narrowing branch A alone would re-create the A/B disagreement L-260913-f28d9d was ruled to remove.

make agent-check and make agent-test both green at 96bb083.

@lchoquel
lchoquel merged commit 1e9912e into dev Sep 13, 2026
2 checks passed
@lchoquel
lchoquel deleted the feature/Scaffold-skill branch September 13, 2026 14:02
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.

1 participant