Repository navigation
Ship the engine as a package (Nix flake first); stop vendoring scripts (#2) - #4
Conversation
packages.gh-project-engine wraps the stdlib-only scripts as
gh-project-{populate,update,lint} CLIs with python3 and gh on their
runtime PATH; version comes from scripts/VERSION. apps expose
nix run .#{populate,update,update-check,lint}; checks.engine-tests runs
the offline suite inside the sandbox (the recorded fake gh makes that
possible), so nix flake check executes all 111 tests. The devShell is
unchanged.
Consumers take this repository as a flake input and pin it in their
flake.lock; upgrading is a deliberate nix flake update github-project.
AI-assisted: Claude Fable 5 (Anthropic)
… flake (#2) The Makefile now finds the engine through whichever channel is available — local scripts/ (the engine repo itself, or a template copy that has not yet detached), gh-project-* CLIs on PATH (a Nix dev shell carrying the engine package, or a future pipx install), or a GHPROJECT_DIR checkout — and fails with a channel-listing error when none is present. make engine-mode prints the resolution for debugging. make init is the one-time detach step for projects created from the template: it deletes the copied engine (scripts py files, _utils, tests, VERSION; scripts/ci stays — the freshness workflow's shell lives there), installs templates/consumer/flake.nix as the project's flake (github-project as a pinned input: dev shell with the CLIs, re-exported apps), and prints the next steps. Prompted by default; INIT_YES=1 for automation. make test degrades to a pointer at the engine repo once the local copy is gone. Verified in a throwaway copy: init leaves only scripts/ci, all three post-init channels resolve (missing-mode error text, checkout mode, PATH mode), and the consumer flake's dev shell and re-exported apps run lint with zero engine files in the tree, pinned by the consumer's own flake.lock. AI-assisted: Claude Fable 5 (Anthropic)
…init (#2) scripts/ci/project-plan-update.sh resolves the engine through the same channels as the Makefile (explicit UPDATE override, local scripts, gh-project-update on PATH, GHPROJECT_DIR checkout) and reports 'could not run' — never fake drift — when none is available. Both workflows gain a hashFiles-guarded step that fetches an engine checkout exactly when the tree carries no local copy, so the same YAML serves the engine repository, pre-init template copies, and detached consumers with no init-time patching. ci.yml also grows an engine-repo-only nix job (install-nix + nix flake check, which builds the package and runs the suite in the sandbox); the setup-python jobs stay Nix-free so consumers are never Nix-bound. test_makefile_stub.py pins the detach lifecycle on a throwaway copy of the tree: init removes exactly the engine files (scripts/ci and the plan survive), installs the consumer flake, drops lock+templates, is idempotent; post-init the stub resolves checkout and PATH channels, errors helpfully when none exists, and make test degrades to a pointer at the engine repo. Guarded to skip inside the Nix sandbox, where only scripts/ is present. Suite: 111 -> 115. AI-assisted: Claude Fable 5 (Anthropic)
README: the quickstart gains the one-time 'make init' detach step; the re-vendor section is replaced by the three consumption channels (flake input with deliberate 'nix flake update github-project' upgrades; GHPROJECT_DIR checkout for non-Nix use; installed CLI planned — the Makefile already resolves gh-project-* from PATH, so that channel needs no consumer-side change when it lands). Layout and design notes now state the engine is referenced, never copied. CLAUDE.md replaces the re-vendor guidance with the single-home rule and tells a session how to recognize a detached consumer tree. The project-setup skill gains the detach step. scripts/VERSION - now also the flake package version - bumps to 0.2.0 for the distribution-model change (engine behavior itself is unchanged). AI-assisted: Claude Fable 5 (Anthropic)
github-project's issue #2 shipped the engine as a flake package (williamdemeo/github-project#4), so the GHPROJECT_DIR checkout wiring this branch introduced is no longer the primary channel: the engine now arrives as a flake input, pinned in flake.lock, and the project-* targets run it through ghproject-{lint,update,update-check} apps this flake re-exports. GHPROJECT_DIR remains as an explicit escape hatch (engine development, Nix-less machines). The lock intentionally pins the engine PR's head revision (the input's original URL already points at the repository default branch): after williamdemeo/github-project#4 merges, a plain 'nix flake update github-project' moves the pin to main. Verified: make project-lint and make project-update-check run the pinned engine (plan lints clean, file current with GitHub), and the GHPROJECT_DIR escape hatch still resolves. One Make gotcha fixed en route: '#' starts a comment even inside an assignment, so the flake attrpaths are written '.\#ghproject-…'. AI-assisted: Claude Fable 5 (Anthropic)
|
The air side of the acceptance is done: formalverification/agda-native-air#93 now carries a commit switching its |
Two environment-dependence bugs the local relaxed-sandbox nix hid: - In a strict sandbox there is no /usr/bin/env, so the recorded fake gh's shebang died with an ENOENT that masqueraded as 'gh: No such file or directory'. patchShebangs tests/fake_gh in the check phase rewrites it to the store python3. Verified with a forced 'nix build --option sandbox true': 115 tests, 4 skipped (the full-tree make tests, absent by design from the scripts-only src). - Under 'make test' the stub tests' sub-makes inherit -w and print Entering/Leaving directory lines into the single-line outputs the assertions read; --no-print-directory on every test invocation. AI-assisted: Claude Fable 5 (Anthropic)
The previously pinned revision's flake check failed on strictly sandboxed nix (missing /usr/bin/env for the test fake's shebang), which would have broken 'nix run .#ghproject-*' for any consumer building the engine there. Still to be repointed to main with 'nix flake update github-project' once williamdemeo/github-project#4 merges. AI-assisted: Claude Fable 5 (Anthropic)
There was a problem hiding this comment.
Pull request overview
Packages the roadmap engine via Nix and replaces vendored consumer copies with channel-based resolution.
Changes:
- Adds the engine package, apps, checks, and consumer flake.
- Introduces
make initand engine-channel resolution. - Updates CI, documentation, tests, and versioning.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
flake.nix |
Defines the engine package, apps, and checks. |
templates/consumer/flake.nix |
Provides consumer-side Nix integration. |
Makefile |
Adds initialization and channel resolution. |
scripts/VERSION |
Bumps the engine to 0.2.0. |
scripts/tests/test_makefile_stub.py |
Tests detachment and engine modes. |
scripts/ci/project-plan-update.sh |
Resolves the engine dynamically. |
.github/workflows/ci.yml |
Adapts CI for detached consumers. |
.github/workflows/project-plan-update.yml |
Fetches an engine for update jobs. |
README.md |
Documents the new distribution model. |
CLAUDE.md |
Updates repository guidance. |
.claude/skills/project-setup/SKILL.md |
Adds engine detachment to setup. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| uses: actions/checkout@v4 | ||
| with: | ||
| repository: williamdemeo/github-project | ||
| path: .github-project-engine |
There was a problem hiding this comment.
Confirmed — the sharpest finding of this review, and the write permissions make it a genuine supply-chain hazard rather than a determinism nit: an upstream push to main became unreviewed code execution with contents:write / pull-requests:write in every detached consumer. Fixed in cb42872: the fetch is now pinned, resolved in order — the github-project rev in the consumer's own flake.lock (so Nix consumers manage exactly one pin, upgraded by the same deliberate nix flake update github-project), the GHPROJECT_ENGINE_REF repository variable, then the default release tag baked into the workflow. Floating main is no longer reachable from either workflow.
|
|
||
| populate-dry: guard-gh ## Preview what populate would create (no mutations) | ||
| $(PYTHON) scripts/gh_project_populate.py $(PLAN) $(FLAGS) --dry-run | ||
| populate-dry: guard-engine guard-gh ## Preview what populate would create (no mutations) |
There was a problem hiding this comment.
Agreed — the path channel's whole point is that the resolved CLI is self-contained (the Nix wrapper prefixes gh onto its own PATH), so probing the caller's ambient PATH produced a false negative for exactly the nix profile install mode the Makefile advertises. Fixed in cb42872: guard-gh now applies only in local/checkout modes, where bare python3 scripts genuinely need an ambient gh; in path mode the CLI's own runtime is authoritative. Verified with a deliberately gh-less PATH (shims + a bare venv): the guard no longer blocks, and execution proceeds into the CLI, which reports its own runtime errors.
| uses: actions/checkout@v4 | ||
| with: | ||
| repository: williamdemeo/github-project | ||
| path: .github-project-engine |
There was a problem hiding this comment.
Agreed — read-only permissions make this the lower-stakes sibling of the freshness-workflow finding, but the determinism argument stands on its own: a consumer commit must not change CI behavior because upstream moved. Fixed in cb42872 with the same pin resolution as there (consumer's flake.lock rev → GHPROJECT_ENGINE_REF variable → default release tag), so both workflows share one story and Nix consumers still have a single pin to manage.
| packages = [ | ||
| pkgs.gnumake | ||
| pkgs.gh | ||
| github-project.packages.${system}.gh-project-engine |
There was a problem hiding this comment.
Agreed — the primary acceptance artifact had only manual verification: nix flake check builds from src = ./scripts and the stub tests skip without the full tree, so nothing automated ever evaluated templates/consumer/flake.nix. Fixed in cb42872: the nix CI job now assembles a detached consumer (make init INIT_YES=1 on a copy), points its github-project input at the engine under test via --override-input path:$GITHUB_WORKSPACE, asserts the Makefile resolves the path channel inside nix develop, and runs lint through both the consumer's dev shell and its re-exported apps. The step script was executed verbatim locally before landing.
…gh guard, smoke-test the consumer flake All four findings were legitimate: - Both workflows' engine fetch was floating upstream main — in the freshness workflow with contents:write and pull-requests:write, so an upstream change became unreviewed code execution with mutation privileges in every detached consumer; in ci.yml it made consumer CI behavior change under an unchanged commit. The fetch is now pinned, resolved in order: the github-project rev in the consumer's own flake.lock (one pin to manage for Nix consumers, upgraded by the same deliberate 'nix flake update github-project'), the GHPROJECT_ENGINE_REF repository variable, the default release tag. - guard-gh probed the caller's ambient PATH even in path mode, failing a 'nix profile install' user whose wrapped CLI carries its own gh. The guard now applies only to local/checkout modes (bare python3 scripts genuinely need ambient gh); verified with a gh-less PATH — execution reaches the CLI, whose own runtime is authoritative. - templates/consumer/flake.nix had no automated coverage (the engine package builds from ./scripts, and the stub tests skip without the full tree). The nix CI job now assembles a detached consumer, points its input at the engine under test via --override-input, and runs lint through the consumer's dev shell and re-exported apps; the step script was executed verbatim locally before landing. AI-assisted: Claude Fable 5 (Anthropic)
Closes #2.
What changes
The engine gets exactly one home. Consumer projects hold data (
docs/GITHUB_PROJECT.md, a thin Makefile, workflows) and reference the engine through a channel; they never carry a copy that can decay.packages.gh-project-engine(CLIsgh-project-{populate,update,lint}with python3 + gh on their wrapped PATH; version =scripts/VERSION), apps fornix run .#{populate,update,update-check,lint}, andchecks.engine-testsrunning the full offline suite inside the sandbox —nix flake checkexecutes all 115 tests.scripts/(this repo, or a not-yet-detached template copy) →gh-project-*CLIs on PATH (Nix dev shell / future pipx) →GHPROJECT_DIRcheckout → a channel-listing error.make engine-modeprints the resolution.make initis the one-time detach for projects created from the template: deletes the copied engine, installstemplates/consumer/flake.nix(github-project as a pinned input: dev shell with the CLIs + re-exported apps), prints next steps. Prompted;INIT_YES=1for automation. Every make target works identically before and after.ci.ymlandproject-plan-update.ymlfetch an engine checkout via ahashFiles-guarded step exactly when the tree has no local copy, so the same YAML serves the engine repo, pre-init copies, and detached consumers — no init-time patching.ci.ymlalso gains an engine-repo-only nix job (nix flake check); the setup-python jobs stay Nix-free so consumers are never Nix-bound.scripts/VERSION→ 0.2.0 (distribution model change; engine behavior unchanged).Verified
nix build+ all four apps run;nix flake checkgreen (115 sandbox tests).test_makefile_stub.py(suite 111 → 115): init removes exactly the engine files, is idempotent, and post-init all three channels resolve (including the acceptance case: a consumer with zero engine files running lint through its own flake's dev shell and re-exported apps, pinned by its ownflake.lock).scripts/ci/project-plan-update.sh's no-engine path reports "could not run", never fake drift.Decisions taken (flagging for review)
make initover prose for the post-create cleanup (the issue left this open): it's testable, one command, and the test suite pins it._utilsstays inside the engine package for now, per the issue's own interim call; a dedicated utilities repo remains future work.main(unpinned) — Nix consumers get real pinning via their lock; a pinned-ref variable can follow if wanted.Follow-up (after this merges)
agda-native-airPR #93 switches itsproject-*targets fromGHPROJECT_DIRto the flake input (a commit on that open branch completes its acceptance criterion; it locks againstmainonce this lands).v0.2.0.AI-assisted development: Claude Fable 5 (Anthropic)