diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 3a22300..80f63f7 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -1,4 +1,10 @@ {"id":"pnt-1q5","title":"Switch nixpkgs URL to optimized channel tarball format","description":"Current nixpkgs input uses github:NixOS/nixpkgs/nixos-unstable (GitHub archive format). Vanixiets uses https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz (channel tarball), which is smaller, faster to fetch, and avoids GitHub rate limiting.\n\nAdopt the tarball URL pattern from vanixiets for the primary nixpkgs input.","status":"closed","priority":2,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-03T15:48:35.584808-05:00","created_by":"Cameron Smith","updated_at":"2026-02-03T18:05:10.871297-05:00","closed_at":"2026-02-03T18:05:10.871297-05:00","close_reason":"Implemented in 293d46d","dependencies":[{"issue_id":"pnt-1q5","depends_on_id":"pnt-h2q","type":"related","created_at":"2026-02-03T15:48:45.065971-05:00","created_by":"Cameron Smith"}]} +{"id":"pnt-2nw","title":"Disambiguate repo-name from package-name in template instantiation","description":"Rename primary package from python-nix-template to pnt-core so repo-name and package-name have distinct placeholder strings. Add repo-name omnix parameter. Eliminates 14+ post-instantiation fix commits observed when instantiating with divergent repo and package names (evidence: sciexp/data init branch).","notes":"\u003c!-- checkpoint-context --\u003e\n## State estimate (2026-02-18, session 2)\n\nWhat was done: Completed definitive classification of all ~109 occurrences of python-nix-template (and variant forms) across the repo. Used sciexp/data fix commits as authoritative evidence: items NOT fixed = package-name contexts (rename to pnt-core), items FIXED = repo-name contexts (leave as python-nix-template for repo-name placeholder). Merged .1 and .2 scope so Nix attribute updates happen alongside the rename to avoid broken intermediate state. Updated all 5 issue descriptions with comprehensive context.\n\nKey classification decisions:\n- Docs prose (\"the python-nix-template project/package/library\" in ~15 .qmd files) classified as REPO-NAME because one docs site per repo. These stay as python-nix-template and become the repo-name placeholder.\n- .ci.json container image names classified as PACKAGE-NAME (not fixed in sciexp/data). Renamed to pnt-core/pnt-core-dev. These are intentionally disabled pending nixpod/nix2container migration.\n- Root package.json name field classified as REPO-NAME (fixed in sciexp/data to @sciexp/data).\n- flake.nix description prefix, docs site titles, README header, bootstrap banner all classified as REPO-NAME (fixed in sciexp/data).\n- template.nix placeholder value updates are in .3's scope, not .1. template.nix om.templates key name stays as python-nix-template.\n- Dummy tag pnt-core-v0.2.1 placed on same commit as python-nix-template-v0.2.1, not HEAD.\n\nWhat was learned: The classification methodology (sciexp/data fix commits as ground truth) is robust. Total: ~51 package-name occurrences to rename, ~32 repo-name occurrences to leave, ~25 platform-identity occurrences classified as repo-name after user decision on docs site identity. snake_case and camelCase variants are used exclusively in package-name contexts. All repo-name and platform-identity occurrences use only kebab-case.\n\nWhat remains: All 5 issues pending implementation. Branch pnt-2nw-disambiguate-names needs creation from pnt-kaf-generalized-discovery. Implementation sequence: .1 (rename + Nix attrs) -\u003e .2 (verification) and .3 (template params) in parallel -\u003e .4 (template refs) -\u003e .5 (integration).\n\nDownstream impact: Once complete, template instantiations with divergent repo-name and package-name will work without post-instantiation fixes.\n\u003c!-- /checkpoint-context --\u003e","status":"open","priority":1,"issue_type":"epic","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-18T14:38:09.771537-05:00","created_by":"Cameron Smith","updated_at":"2026-02-18T15:17:42.700934-05:00"} +{"id":"pnt-2nw.1","title":"Rename primary package to pnt-core and update Nix attributes","description":"Rename primary package from python-nix-template to pnt-core, updating all package-name contexts. Scope expanded to include Nix attribute updates (originally pnt-2nw.2) because leaving Nix attrs pointing at the old directory breaks nix build between issues.\n\nCRITICAL: Selective rename, NOT blanket find-and-replace. Only package-name contexts change. Repo-name contexts must remain as python-nix-template — they become the repo-name placeholder in pnt-2nw.3. Docs prose (\"the python-nix-template project/package/library\" in ~15 .qmd files) is classified as repo-name because there is one docs site per repo.\n\n--- STEP 1: DIRECTORY RENAMES (git mv) ---\n\ngit mv packages/python-nix-template packages/pnt-core\ngit mv packages/pnt-core/src/python_nix_template packages/pnt-core/src/pnt_core\n\n--- STEP 2: FILE CONTENT — RENAME TO pnt-core/pnt_core/pntCore ---\n\nINSIDE packages/pnt-core/:\n- pyproject.toml line 7: name \"python-nix-template\" -\u003e \"pnt-core\"\n- pyproject.toml line 13: entry point python-nix-template = \"python_nix_template:main\" -\u003e pnt-core = \"pnt_core:main\"\n- pyproject.toml line 20: packages [\"src/python_nix_template\"] -\u003e [\"src/pnt_core\"]\n- pyproject.toml line 63: editable source python-nix-template -\u003e pnt-core\n- pyproject.toml line 85: pixi name \"python-nix-template\" -\u003e \"pnt-core\"\n- pyproject.toml line 106: --cov=src/python_nix_template/ -\u003e --cov=src/pnt_core/\n- pyproject.toml line 113: test path \"src/python_nix_template/tests\" -\u003e \"src/pnt_core/tests\"\n- pyproject.toml line 118: coverage omit \"src/python_nix_template/tests/*\" -\u003e \"src/pnt_core/tests/*\"\n- package.json line 16: \"name\": \"python-nix-template\" -\u003e \"pnt-core\"\n- src/pnt_core/__init__.py line 12: print string python-nix-template -\u003e pnt-core\n- src/pnt_core/tests/test_main.py line 4: comment python_nix_template -\u003e pnt_core\n- src/pnt_core/tests/test_main.py line 12: assertion \"Hello from python-nix-template!\" -\u003e \"Hello from pnt-core!\"\n- conda/README.md lines 19, 23: conda env name python-nix-template -\u003e pnt-core\n- .ci.json line 3: image names [\"python-nix-template\", \"python-nix-template-dev\"] -\u003e [\"pnt-core\", \"pnt-core-dev\"]\n\nNIX MODULES (absorbed from pnt-2nw.2):\n- modules/packages.nix: pythonNixTemplate312 -\u003e pntCore312, pythonNixTemplate313 -\u003e pntCore313, mkVirtualEnv names \"python-nix-template-3.1x\" -\u003e \"pnt-core-3.1x\", default alias\n- modules/devshell.nix: same pattern — attr names, devshell name strings, default alias\n\nDOCUMENTATION:\n- docs/_quarto.yml line 118: package: python_nix_template -\u003e pnt_core (quartodoc)\n- docs/_quarto.yml line 127: \"API reference for python-nix-template.\" -\u003e \"API reference for pnt-core.\"\n- docs/tutorials/getting-started.qmd line 26: print example -\u003e pnt-core\n- docs/guides/installation.qmd lines 15, 23: pip install python-nix-template -\u003e pnt-core\n- docs/guides/installation.qmd lines 46-47: import python-nix-template -\u003e import pnt_core (fix pre-existing bug: use snake_case for Python import), python-nix-template.__version__ -\u003e pnt_core.__version__\n- docs/notes/architecture/package-distribution-channels.md: line 26 heading, lines 30-31 paths, line 56 default name — all python-nix-template -\u003e pnt-core\n- docs/notes/architecture/crane-uv2nix-integration.md line 413: python-nix-template -\u003e pnt-core in pure Python package list\n\nJUSTFILE:\n- 19 recipe package= defaults (lines 253-408): package=\"python-nix-template\" -\u003e package=\"pnt-core\"\n Recipes: conda-build, conda-env, pixi-lock, conda-lock, conda-test, conda-lint, conda-lint-fix, conda-type, conda-check, test, uv-build, uv-sync, uv-lock, lint, lint-fix, type, check\n- Line 731: test-package-release package-name=\"python-nix-template\" -\u003e package-name=\"pnt-core\"\n\nREADME.md:\n- Recipe examples with package= (lines 265-273, 303-312): python-nix-template -\u003e pnt-core\n- Line 317: test-package-release package-name=\"python-nix-template\" -\u003e package-name=\"pnt-core\"\n\nOTHER:\n- .vscode/launch.json line 5: debug name \"python-nix-template: Debug CLI\" -\u003e \"pnt-core: Debug CLI\"\n- .vscode/launch.json line 8: module \"python_nix_template\" -\u003e \"pnt_core\"\n- packages/pnt-functional/conda/README.md lines 19, 23: env name python-nix-template -\u003e pnt-core\n\n--- STEP 3: WHAT TO LEAVE AS python-nix-template ---\n\nThese repo-name contexts become the repo-name placeholder in pnt-2nw.3:\n- Root package.json: name and repository URL\n- packages/pnt-core/package.json line 85: repository URL\n- packages/pnt-cli/package.json line 85: repository URL\n- packages/pnt-functional/package.json line 85: repository URL\n- modules/containers.nix line 8: repoName\n- modules/template.nix: welcomeText (line 9), om.templates key (line 82), placeholder values (lines 88/93/98 — updated by pnt-2nw.3, not here)\n- wrangler.jsonc: worker name and domain (lines 3, 18)\n- .github/ISSUE_TEMPLATE/config.yml: GitHub discussion URLs\n- .github/workflows/deploy-docs.yaml line 70: worker URLs\n- .github/workflows/template.yaml lines 141, 182: REPO_REF\n- .dvc/config line 5: GCS path\n- scripts/bootstrap.sh: comment (line 2), raw.githubusercontent URL (line 4), banner (line 11)\n- scripts/sops-bootstrap.sh: SSH key comment (line 38), echo (line 175)\n- justfile: repo= defaults (lines 41, 56), SOPS example (line 517), template init ref (line 614), deployment URLs (lines 884, 907, 947)\n- README.md: header (line 1), flake refs (lines 14/43/82/98-101), develop context (line 208), repo= examples (lines 254-255)\n- flake.nix line 2: description prefix\n- docs/_quarto.yml: title (line 42), site-url (line 44), GitHub links (lines 53, 62)\n- docs/index.qmd: title (line 2), heading (line 9), GitHub link (line 26)\n- docs/guides/installation.qmd: clone URL (line 31), cd (line 32), description/prose (lines 3, 8)\n- docs/tutorials/getting-started.qmd: description and prose (lines 3, 6, 8)\n- docs/guides/development.qmd: description and prose (lines 3, 8)\n- All docs prose in ~15 .qmd files: \"the python-nix-template project/package/library\" references\n- docs/about/index.qmd, docs/concepts/index.qmd, docs/development/**/*.qmd: all prose references\n\n--- STEP 4: DUMMY TAG ---\n\ngit tag pnt-core-v0.2.1 $(git rev-parse python-nix-template-v0.2.1)\nPlace on SAME commit as existing tag, NOT on HEAD. For version contiguity.\n\n--- STEP 5: VERIFICATION ---\n\nnix build\nnix flake check\ncd packages/pnt-core \u0026\u0026 pytest\n\nAll must pass before marking complete.\n\n--- BRANCH SETUP ---\n\nWork in .worktrees/pnt-2nw-1-rename-package\ngit worktree add .worktrees/pnt-2nw-1-rename-package -b pnt-2nw-1-rename-package pnt-2nw-disambiguate-names","status":"open","priority":1,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-18T14:38:19.514833-05:00","created_by":"Cameron Smith","updated_at":"2026-02-18T15:16:26.0567-05:00","dependencies":[{"issue_id":"pnt-2nw.1","depends_on_id":"pnt-2nw","type":"parent-child","created_at":"2026-02-18T14:38:19.515572-05:00","created_by":"Cameron Smith"}]} +{"id":"pnt-2nw.2","title":"Verify readDir-based discovery works with renamed pnt-core package","description":"Verification-only pass after pnt-2nw.1. All rename and Nix attribute work is done in .1. This issue confirms that the readDir-based discovery mechanisms work correctly with the renamed package.\n\nVerify:\n1. modules/python.nix readDir discovers packages/pnt-core/ correctly\n2. nix eval .#packages.x86_64-linux shows pntCore312 and pntCore313 attributes\n3. nix eval .#devShells.x86_64-linux shows pntCore312 and pntCore313 devshells\n4. No Nix module references the old python-nix-template package directory name\n5. The discover-packages CI script (if it exists) correctly detects pnt-core\n\nSearch for any remaining hardcoded references to the old name:\n rg 'python.nix.template' modules/ --type nix\n rg 'python_nix_template' modules/ --type nix\n rg 'pythonNixTemplate' modules/ --type nix\n\nThese should return zero results in Nix modules (the string \"python-nix-template\" will still appear in non-package contexts like repoName, om.templates key, etc. — those are correct).","status":"open","priority":1,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-18T14:38:27.11666-05:00","created_by":"Cameron Smith","updated_at":"2026-02-18T15:16:41.590114-05:00","dependencies":[{"issue_id":"pnt-2nw.2","depends_on_id":"pnt-2nw","type":"parent-child","created_at":"2026-02-18T14:38:27.117365-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-2nw.2","depends_on_id":"pnt-2nw.1","type":"blocks","created_at":"2026-02-18T14:38:55.803552-05:00","created_by":"Cameron Smith"}]} +{"id":"pnt-2nw.3","title":"Add repo-name parameter and update template.nix placeholders","description":"In modules/template.nix: add new string param repo-name with placeholder python-nix-template and description \"Repository name (kebab-case)\". This is a REQUIRED parameter (not optional with default).\n\nChange placeholder values to match the renamed package:\n- package-name-kebab-case placeholder: python-nix-template -\u003e pnt-core\n- package-name-snake-case placeholder: python_nix_template -\u003e pnt_core\n- package-name-camel-case placeholder: pythonNixTemplate -\u003e pntCore\n\nUpdate template tests to include repo-name param in the test params.\n\nAfter this change, omnix template instantiation performs two sets of substitutions:\n1. All occurrences of \"pnt-core\" (and pnt_core, pntCore) -\u003e user's package name\n2. All occurrences of \"python-nix-template\" -\u003e user's repo name\n\nVerify that all repo-name contexts in template files still contain the literal string \"python-nix-template\", confirming they will be substituted with the user's repo name. Key repo-name contexts:\n- Root package.json name and repository URL\n- packages/*/package.json repository URLs\n- modules/containers.nix repoName\n- wrangler.jsonc worker name and domain\n- .github/ issue template URLs, deploy-docs worker URLs, template.yaml REPO_REF\n- .dvc/config GCS path\n- scripts/bootstrap.sh raw.githubusercontent URL and banner\n- scripts/sops-bootstrap.sh SSH key comment\n- justfile repo= defaults, SOPS example, template init ref, deployment URLs\n- README.md header, flake refs, repo= examples, develop context\n- flake.nix description prefix\n- docs/_quarto.yml title, site-url, GitHub links\n- docs/index.qmd title, heading, GitHub link\n- docs/guides/installation.qmd clone URL and cd command\n- All docs prose in ~15 .qmd files (\"the python-nix-template project/package/library\") — classified as repo-name because one docs site per repo\n- docs/tutorials, docs/guides, docs/about, docs/concepts, docs/development prose references","status":"open","priority":1,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-18T14:38:35.063916-05:00","created_by":"Cameron Smith","updated_at":"2026-02-18T15:16:59.035277-05:00","dependencies":[{"issue_id":"pnt-2nw.3","depends_on_id":"pnt-2nw","type":"parent-child","created_at":"2026-02-18T14:38:35.064575-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-2nw.3","depends_on_id":"pnt-2nw.1","type":"blocks","created_at":"2026-02-18T14:38:55.948661-05:00","created_by":"Cameron Smith"}]} +{"id":"pnt-2nw.4","title":"Handle template reference contexts in justfile and docs","description":"Handle template reference contexts where both git-org (sciexp) and repo-name (python-nix-template) placeholders appear in the same string, causing potential double-substitution issues.\n\nTwo specific cases:\n1. docs/notes/archive/README.md contains multiple \"github:sciexp/python-nix-template\" flake references. Fix by adding docs/notes/archive/ to the nix-template path toggle in template.nix so this directory is excluded from template output by default (it contains template-specific documentation that should not appear in instantiated projects).\n\n2. justfile line 614 init recipe contains \"github:sciexp/python-nix-template\" as an embedded default value. Fix by restructuring the recipe to take template-ref as a required parameter rather than embedding the placeholder-containing string as a default.\n\nNote: After pnt-2nw.3, \"sciexp\" is the git-org placeholder and \"python-nix-template\" is the repo-name placeholder. Strings like \"github:sciexp/python-nix-template\" undergo double substitution (both placeholders replaced), which is correct for these contexts. The issue is specifically about strings where the template should reference ITSELF (the upstream template repo) rather than being substituted.","status":"open","priority":2,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-18T14:38:42.754058-05:00","created_by":"Cameron Smith","updated_at":"2026-02-18T15:17:12.270617-05:00","dependencies":[{"issue_id":"pnt-2nw.4","depends_on_id":"pnt-2nw","type":"parent-child","created_at":"2026-02-18T14:38:42.754765-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-2nw.4","depends_on_id":"pnt-2nw.3","type":"blocks","created_at":"2026-02-18T14:38:56.110897-05:00","created_by":"Cameron Smith"}]} +{"id":"pnt-2nw.5","title":"Integration verification and instantiation test documentation","description":"Integration verification after all rename and template parameter changes. Verify:\n\n1. nix build, nix flake check, and pytest pass after all changes\n2. Template instantiation works for convergent case (repo-name == package-name):\n Test with omnix init using identical repo-name and package-name values\n3. Template instantiation works for divergent case (repo-name != package-name):\n Test with e.g. repo-name=data, package-name=omicsio — this is the case that motivated the entire epic\n4. In both cases, verify no \"python-nix-template\" or \"pnt-core\" literal strings remain in the instantiated output (all should be substituted)\n5. Verify fix-template-names utility in template-rename.nix still works, or update if needed\n6. Document the verification commands and results\n\nCross-reference with sciexp/data fix commits (git -C ~/projects/sciexp/data log --oneline --reverse init --not main) to confirm the same contexts that required manual fixing would now be handled automatically by the two-placeholder system.","status":"open","priority":1,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-18T14:38:49.792574-05:00","created_by":"Cameron Smith","updated_at":"2026-02-18T15:17:23.223935-05:00","dependencies":[{"issue_id":"pnt-2nw.5","depends_on_id":"pnt-2nw","type":"parent-child","created_at":"2026-02-18T14:38:49.793345-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-2nw.5","depends_on_id":"pnt-2nw.2","type":"blocks","created_at":"2026-02-18T14:38:56.289628-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-2nw.5","depends_on_id":"pnt-2nw.3","type":"blocks","created_at":"2026-02-18T14:38:56.464683-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-2nw.5","depends_on_id":"pnt-2nw.4","type":"blocks","created_at":"2026-02-18T14:38:56.621874-05:00","created_by":"Cameron Smith"}]} {"id":"pnt-3bd","title":"Add crane.inputs.nixpkgs.follows and align cache declarations with vanixiets","description":"Two related flake hygiene items:\n\n1. crane input has no follows directive, causing a redundant independent nixpkgs evaluation. Since crane is a build library with no upstream cache, adding inputs.nixpkgs.follows = nixpkgs eliminates the extra eval with no cache downside.\n\n2. Cache declarations are minimal (3 substituters) compared to vanixiets (9). Add cache.nixos.org explicitly, numtide.cachix.org, and cameronraysmith.cachix.org to nixConfig.","status":"closed","priority":2,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-03T15:48:33.582737-05:00","created_by":"Cameron Smith","updated_at":"2026-02-03T18:04:49.646325-05:00","closed_at":"2026-02-03T18:04:49.646325-05:00","close_reason":"Implemented in e3a9997, added crane/numtide/cache.nixos.org caches","dependencies":[{"issue_id":"pnt-3bd","depends_on_id":"pnt-h2q","type":"related","created_at":"2026-02-03T15:48:44.907425-05:00","created_by":"Cameron Smith"}]} {"id":"pnt-3jf","title":"Validate sciexp/data platform instantiation","description":"Final validation gate before v0.2.0 release. Instantiate template to create the sciexp data platform repository.\n\nTarget: ~/projects/sciexp/data\n\nValidation steps:\n- Instantiate template with configuration for omicsio, scilake, scimesh package structure\n- Verify independent locks work for each package\n- Verify Nix builds succeed for all packages\n- Verify CI patterns work correctly\n- Document any issues found and fix if minor, or create follow-up issues if significant\n\nThis issue is blocked by the other four polish issues. Successful completion means we are ready to manually create the v0.2.0 release tag.\n\nReference: ~/projects/sciexp/planning/contexts/sciexp-data-platform.md for sciexp/data requirements\n\nScope: ~3 hours","status":"closed","priority":1,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-05T13:48:01.905707-05:00","created_by":"Cameron Smith","updated_at":"2026-02-05T15:14:16.459237-05:00","closed_at":"2026-02-05T15:14:16.459237-05:00","close_reason":"Validated: template evaluation, test infrastructure (11/11 tests pass across 3 packages), and instantiation workflow confirmed working. Welcome text and README are consistent. 619014b","dependencies":[{"issue_id":"pnt-3jf","depends_on_id":"pnt-6yk","type":"blocks","created_at":"2026-02-05T13:48:10.890329-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-3jf","depends_on_id":"pnt-5wx","type":"blocks","created_at":"2026-02-05T13:48:11.058701-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-3jf","depends_on_id":"pnt-m7j","type":"blocks","created_at":"2026-02-05T13:48:11.23594-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-3jf","depends_on_id":"pnt-bxq","type":"blocks","created_at":"2026-02-05T13:48:11.38108-05:00","created_by":"Cameron Smith"}]} {"id":"pnt-3qp","title":"Investigate and validate optimal test strategy for PyO3 + Python hybrid packages","description":"The current devShell excludes pnt-cli from uv2nix editableOverlay because maturin packages are incompatible with pyprojectFixupEditableHook (expects EDITABLE_ROOT which maturin does not set). This means nix develop -c pytest cannot import pnt_cli._native, failing both locally and in CI.\n\nThis task requires investigation and experimentation to determine the optimal testing strategy for packages containing both Python and Rust code via PyO3 extension modules, using maturin as the build backend and crane for Rust build artifact caching.\n\nQuestions to answer through experimentation:\n\n1. Can we install pnt-cli non-editable in the devShell virtualenv (pre-built wheel via Nix) so pytest can import _native? What are the trade-offs for developer iteration speed?\n\n2. What is the correct split between cargo nextest (pure Rust unit tests in crates/) and pytest (Python integration tests that exercise the PyO3 bindings)? Should these be separate CI jobs or a unified test recipe?\n\n3. Can maturin develop be integrated into the devShell shellHook or a just recipe to build the extension in-place for iterative development? How does this interact with the uv2nix virtualenv?\n\n4. How do the reference implementations handle this?\n - ~/projects/nix-workspace/uv2nix — upstream patterns for maturin/PyO3 packages\n - ~/projects/nix-workspace/pyproject.nix — Python project tooling for Nix\n - ~/projects/nix-workspace/nix-cargo-crane — crane patterns for Rust builds\n - ~/projects/rust-workspace/ironstar — crane devShell integration (Rust-only but relevant caching patterns)\n\n5. For the template test (omnix init + validate): should instantiated templates use nix flake check (builds everything through Nix including maturin wheel), nix develop -c uv run pytest (uv builds the wheel), or a combination?\n\n6. Does the nix build path (nix flake check / nix build) already exercise both Rust compilation (via crane cargoArtifacts) and Python tests (via pytest in check phase)? If so, it may be sufficient for CI validation without needing devShell-based testing.\n\nAcceptance criteria:\n- Document the chosen test strategy with rationale\n- Validate that both cargo nextest (Rust unit tests) and pytest (Python integration tests) pass\n- Validate the strategy works both locally (nix develop) and in CI (template instantiation test)\n- Update devshell.nix, justfile, and template.yaml as needed to implement the chosen strategy\n- Ensure crane artifact caching is preserved (no unnecessary Rust rebuilds)","status":"closed","priority":2,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-03T10:20:11.038346-05:00","created_by":"Cameron Smith","updated_at":"2026-02-03T11:07:51.054773-05:00","closed_at":"2026-02-03T11:07:51.054773-05:00","close_reason":"Resolved via test relocation: pnt-cli tests moved from src/pnt_cli/tests/ to tests/ (outside source namespace). This avoids source tree shadowing the installed wheel with _native.so. Strategy: Rust tests via cargo nextest (devShell + crane checks), Python tests via pytest from package dir (installed wheel). maturin develop not needed. nix flake check validates both build paths.","dependencies":[{"issue_id":"pnt-3qp","depends_on_id":"pnt-edl","type":"child-of","created_at":"2026-02-03T10:20:20.918483-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-3qp","depends_on_id":"pnt-j6f","type":"blocked-by","created_at":"2026-02-03T10:20:21.103095-05:00","created_by":"Cameron Smith"}]} @@ -46,14 +52,14 @@ {"id":"pnt-h2o","title":"Migrate from yarn to bun monorepo for semantic-release tooling","description":"The python-nix-template currently uses yarn@4.6.0 as its JavaScript package manager for semantic-release tooling, while vanixiets and typescript-nix-template both use bun@1.3.4. This divergence:\n- Complicates porting scripts between repositories (e.g., preview-version.sh needed bun-to-yarn adaptation)\n- Means the python-nix-template cannot use the exact same scripts/preview-version.sh as the other repos\n- Introduces yarn-specific configuration (.yarnrc.yml, yarn.lock) that differs from the ecosystem standard\n\nMigration scope:\n- Replace yarn.lock with bun.lockb\n- Update packageManager field in all package.json files from yarn to bun\n- Update all justfile recipes that reference yarn (preview-version, release-package, test-package-release, etc.)\n- Update scripts/preview-version.sh to use bun instead of yarn\n- Update CI workflows referencing yarn\n- Remove .yarnrc.yml and yarn-specific configuration\n- Verify semantic-release, semantic-release-monorepo, and all plugins work correctly under bun\n\nReference: vanixiets and typescript-nix-template for the target bun configuration.","status":"closed","priority":2,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-04T20:34:43.898129-05:00","created_by":"Cameron Smith","updated_at":"2026-02-04T21:09:06.495921-05:00","closed_at":"2026-02-04T21:09:06.495921-05:00","close_reason":"Implemented in 06324ed. Migrated from yarn@4.6.0 to bun@1.3.4 across devShell, package.json (root + 3 packages), justfile, preview-version.sh, CI workflows, gitignore, and gitattributes.","dependencies":[{"issue_id":"pnt-h2o","depends_on_id":"pnt-wbq","type":"discovered-from","created_at":"2026-02-04T20:34:49.100152-05:00","created_by":"Cameron Smith"}]} {"id":"pnt-h2q","title":"Remove nixpkgs follows from pyproject-nix chain to restore cache hits","description":"The pyproject-nix, uv2nix, and pyproject-build-systems inputs all declare inputs.nixpkgs.follows = nixpkgs, which forces them to use our nixpkgs revision. Since pyproject-nix.cachix.org builds against their own pinned nixpkgs, the follows override causes derivation hash mismatches, forcing source rebuilds of maturin, python-libcst, and other build-system packages.\n\nRemove the nixpkgs follows from all three inputs while keeping the internal cross-references (pyproject-build-systems.inputs.pyproject-nix.follows and .uv2nix.follows) since those are co-released.\n\nBefore: source rebuilds of maturin/libcst on every devshell entry.\nAfter: cache hits from pyproject-nix.cachix.org for build-system packages.\nTradeoff: second nixpkgs evaluation during flake eval (acceptable for build tool cache hits).","status":"closed","priority":1,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-03T15:48:30.164416-05:00","created_by":"Cameron Smith","updated_at":"2026-02-03T18:04:48.434561-05:00","closed_at":"2026-02-03T18:04:48.434561-05:00","close_reason":"Implemented in e3a9997"} {"id":"pnt-j6f","title":"Migrate to dendritic flake-parts with import-tree","description":"Restructure nix module architecture to match reference implementations (vanixiets, ironstar, typescript-nix-template).\n\nChanges required:\n- Add import-tree as flake input (github:vic/import-tree or equivalent)\n- Move nix/modules/*.nix to modules/*.nix (top-level)\n- Replace readDir-based discovery in flake.nix with: flake-parts.lib.mkFlake { inherit inputs; } (inputs.import-tree ./modules)\n- Add systems.nix module (extract system list from flake.nix)\n- Add flake-parts.nix module (external module imports like nix-unit)\n- Keep nix/packages/pnt-cli/ and nix/lib/ as non-module utilities (imported explicitly by python.nix)\n- Update all relative paths in modules that reference nix/packages/ or nix/lib/ (now one level up)\n- containers.nix is 355 lines — evaluate splitting into containers/dev.nix and containers/production.nix\n- Update template.nix conditional paths to reflect new module locations\n- Verify nix flake check passes after restructure\n- Update .github/workflows/ path filters if they reference nix/modules/\n\nKey architectural decisions:\n- Modules communicate through _module.args (python.nix exports) and config namespace (pre-commit.devShell)\n- import-tree auto-discovers all .nix files in modules/ tree\n- No manual imports list in flake.nix — adding a file to modules/ auto-includes it\n- nix/packages/ stays as explicit utility imports (not auto-discovered modules)","status":"closed","priority":2,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-03T10:10:03.187287-05:00","created_by":"Cameron Smith","updated_at":"2026-02-03T11:07:43.74843-05:00","closed_at":"2026-02-03T11:07:43.74843-05:00","close_reason":"Implemented in b27f3c8. Dendritic migration, root pyproject.toml removal, ruff.toml, per-package justfile recipes, editable root fix.","dependencies":[{"issue_id":"pnt-j6f","depends_on_id":"pnt-edl","type":"child-of","created_at":"2026-02-03T10:10:17.67297-05:00","created_by":"Cameron Smith"}],"comments":[{"id":6,"issue_id":"pnt-j6f","author":"Cameron Smith","text":"Removing root pyproject.toml: vestigial workspace declaration contradicts federation model in python.nix","created_at":"2026-02-03T16:01:31Z"}]} -{"id":"pnt-kaf","title":"Generalize package discovery to eliminate hardcoded names","description":"Replace all hardcoded package names (pnt-cli, pnt-functional, python-nix-template) across Nix modules, justfile, CI workflows, and mergify with dynamic discovery based on builtins.readDir and Cargo.toml presence detection.\n\n## Design\n\nThe canonical marker for maturin/pyo3 packages is `packages/{name}/Cargo.toml`. All layers discover packages dynamically:\n\n- Nix modules enumerate `packages/` via `builtins.readDir`, classify by `Cargo.toml` presence\n- Justfile recipes lose hardcoded defaults, `list-packages-json` annotates maturin status\n- CI `discover-packages` step adds `is_maturin` field to matrix output\n- Mergify conditions no longer reference specific package names\n- Convention: each maturin package requires `nix/packages/{name}/default.nix` returning `{ overlay, checks }`\n- Flake inputs (crane, crane-maturin) remain always present regardless of template toggle\n\n## Relationship to pnt-wl6\n\nThis epic may supersede some pnt-wl6 children (wl6.8, wl6.12, wl6.13, wl6.3). Reassess pnt-wl6 after this epic completes.\n\n\u003c!-- stigmergic-signals --\u003e\n| Signal | Value | Updated |\n|---|---|---|\n| schema-version | 1 | 2026-02-18 |\n| cynefin | complicated | 2026-02-18 |\n| surprise | 0.0 | 2026-02-18 |\n| progress | not-started | 2026-02-18 |\n| escalation | none | -- |\n| planning-depth | medium | 2026-02-18 |\n\u003c!-- /stigmergic-signals --\u003e","status":"open","priority":2,"issue_type":"epic","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-18T11:51:28.720914-05:00","created_by":"Cameron Smith","updated_at":"2026-02-18T11:51:43.949969-05:00"} -{"id":"pnt-kaf.1","title":"Replace hardcoded package enumeration in python.nix with readDir discovery","description":"Core Nix module change. Replace all hardcoded `builtins.pathExists ../packages/pnt-cli` and `builtins.pathExists ../packages/pnt-functional` declarations in `modules/python.nix` with dynamic discovery using `builtins.readDir ../packages`.\n\n## Scope\n\n- Add package discovery logic: enumerate `packages/` via `builtins.readDir`, filter directories, classify as maturin (`Cargo.toml` present) vs pure Python\n- Generalize `packageWorkspaces`: iterate discovered package names through `loadPackage` instead of hardcoding three entries (lines 45-53)\n- Generalize `mkPackageModule`: iterate `maturinPackageNames`, import `nix/packages/{name}/default.nix` for each, compose overlays and checks (lines 65-74)\n- Generalize `mkPythonSet` overlay composition: replace `lib.optional hasCli packageWorkspaces.pnt-cli.overlay` pattern with iteration over discovered packages (lines 91-104)\n- Generalize `mkEditablePythonSet`: iterate `purePackageNames` only (maturin packages excluded per existing convention at line 107-110)\n- Generalize `rustChecks` collection: iterate all maturin package modules (lines 146-150)\n- Expose `hasMaturinPackages`, `maturinPackageNames`, `purePackageNames` via `_module.args` for devshell.nix and containers.nix\n- Add eval-time error when `Cargo.toml` exists but `nix/packages/{name}/default.nix` is missing\n- The base package name (currently `python-nix-template`) is the omnix placeholder — it must participate in discovery like any other package, not be hardcoded separately\n\n## Acceptance criteria\n\n- `nix eval .#packages.x86_64-linux --json | jq 'keys'` succeeds and includes all existing package outputs\n- `nix eval .#checks.x86_64-linux --json | jq 'keys'` includes pnt-cli checks when packages/pnt-cli exists\n- `nix build .#packages.x86_64-linux.default` succeeds\n- Adding a new pure Python package to `packages/` is picked up without editing python.nix\n- Removing `packages/pnt-cli` causes maturin-related overlays and checks to disappear without errors\n\n## Verification\n\n```bash\nnix flake check --no-build\nnix eval .#packages.x86_64-darwin --json | jq 'keys'\nnix eval .#checks.x86_64-darwin --json | jq 'keys'\n```\n\n\u003c!-- stigmergic-signals --\u003e\n| Signal | Value | Updated |\n|---|---|---|\n| schema-version | 1 | 2026-02-18 |\n| cynefin | complicated | 2026-02-18 |\n| surprise | 0.0 | 2026-02-18 |\n| progress | not-started | 2026-02-18 |\n| escalation | none | -- |\n| planning-depth | medium | 2026-02-18 |\n\u003c!-- /stigmergic-signals --\u003e","status":"open","priority":2,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-18T11:52:02.76287-05:00","created_by":"Cameron Smith","updated_at":"2026-02-18T11:52:02.76287-05:00","dependencies":[{"issue_id":"pnt-kaf.1","depends_on_id":"pnt-kaf","type":"parent-child","created_at":"2026-02-18T11:52:02.7641-05:00","created_by":"Cameron Smith"}]} -{"id":"pnt-kaf.2","title":"Replace hardcoded hasCli in devshell.nix with discovery-based detection","description":"Replace `hasCli = builtins.pathExists ../packages/pnt-cli` in `modules/devshell.nix` (line 18) with `hasMaturinPackages` consumed from `_module.args` as exposed by the generalized python.nix.\n\n## Scope\n\n- Remove local `hasCli` declaration\n- Consume `hasMaturinPackages` from `_module.args` (provided by pnt-kaf.1)\n- Replace `lib.optionals hasCli` (line 58) with `lib.optionals hasMaturinPackages`\n- Update comment on line 61 from \"Rust tooling for pnt-cli pyo3 extension\" to generic \"Rust tooling for maturin/pyo3 packages\"\n\n## Acceptance criteria\n\n- `nix develop` enters devshell successfully\n- When `packages/pnt-cli` exists: cargo, rustc, clippy, cargo-nextest, maturin are on PATH\n- When no maturin packages exist: Rust tools absent from PATH\n- No hardcoded reference to `pnt-cli` remains in devshell.nix\n\n## Verification\n\n```bash\nnix develop -c which cargo # should succeed when maturin packages exist\nnix develop -c which maturin\n```\n\n\u003c!-- stigmergic-signals --\u003e\n| Signal | Value | Updated |\n|---|---|---|\n| schema-version | 1 | 2026-02-18 |\n| cynefin | complicated | 2026-02-18 |\n| surprise | 0.0 | 2026-02-18 |\n| progress | not-started | 2026-02-18 |\n| escalation | none | -- |\n| planning-depth | medium | 2026-02-18 |\n\u003c!-- /stigmergic-signals --\u003e","status":"open","priority":2,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-18T11:52:14.654722-05:00","created_by":"Cameron Smith","updated_at":"2026-02-18T11:52:14.654722-05:00","dependencies":[{"issue_id":"pnt-kaf.2","depends_on_id":"pnt-kaf","type":"parent-child","created_at":"2026-02-18T11:52:14.655494-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-kaf.2","depends_on_id":"pnt-kaf.1","type":"blocks","created_at":"2026-02-18T11:53:31.425769-05:00","created_by":"Cameron Smith"}]} -{"id":"pnt-kaf.3","title":"Replace hardcoded container definitions in containers.nix with discovery","description":"Replace the hardcoded `productionContainerDefs` attrset in `modules/containers.nix` (lines 14-20) with a discovery-based mechanism that derives container definitions from per-package metadata rather than naming `pnt-cli` explicitly.\n\n## Scope\n\n- Remove local `hasCli = builtins.pathExists ../packages/pnt-cli` declaration (line 10)\n- Replace hardcoded `productionContainerDefs` with discovery. Two approaches to evaluate:\n a) Per-package `.container.nix` or `.ci.json` metadata declaring entrypoint and description\n b) Derive from `[project.scripts]` in `pyproject.toml` via Nix evaluation\n Option (a) aligns with existing `.ci.json` convention used in CI discover-packages.\n- Consume `maturinPackageNames` or similar from `_module.args` if container definitions should be restricted to maturin packages, or generalize to allow any package to declare a container\n- `containerMatrix` flake output should reflect dynamically discovered containers\n\n## Acceptance criteria\n\n- `nix eval .#containerMatrix --json` reflects discovered packages\n- No hardcoded reference to `pnt-cli` remains in containers.nix\n- When `packages/pnt-cli` exists with container metadata: container appears in matrix\n- When no container-capable packages exist: empty matrix, no errors\n\n## Verification\n\n```bash\nnix eval .#containerMatrix --json | jq .\n```\n\n\u003c!-- stigmergic-signals --\u003e\n| Signal | Value | Updated |\n|---|---|---|\n| schema-version | 1 | 2026-02-18 |\n| cynefin | complicated | 2026-02-18 |\n| surprise | 0.0 | 2026-02-18 |\n| progress | not-started | 2026-02-18 |\n| escalation | none | -- |\n| planning-depth | medium | 2026-02-18 |\n\u003c!-- /stigmergic-signals --\u003e","status":"open","priority":2,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-18T11:52:26.815361-05:00","created_by":"Cameron Smith","updated_at":"2026-02-18T11:52:26.815361-05:00","dependencies":[{"issue_id":"pnt-kaf.3","depends_on_id":"pnt-kaf","type":"parent-child","created_at":"2026-02-18T11:52:26.816289-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-kaf.3","depends_on_id":"pnt-kaf.1","type":"blocks","created_at":"2026-02-18T11:53:31.581192-05:00","created_by":"Cameron Smith"}]} -{"id":"pnt-kaf.4","title":"Remove hardcoded pnt-cli defaults from justfile Rust and container recipes","description":"Remove hardcoded `pnt-cli` default values from justfile Rust and container recipe parameters. Extend `list-packages-json` to annotate maturin status.\n\n## Scope\n\n- Remove `=\"pnt-cli\"` defaults from: cargo-build, cargo-test, cargo-clippy, cargo-nextest, cargo-check (lines 409-430)\n- Remove `CONTAINER=\"pnt-cli\"` defaults from: container-build-production, container-load-production, container-push-production (lines ~296-307)\n- Rust and container recipes become required-parameter recipes (invoker must specify package name)\n- Extend `list-packages-json` recipe to emit `maturin` boolean field per package based on `Cargo.toml` presence\n- Consider adding a `list-maturin-packages` convenience recipe that filters to maturin-only packages\n\n## Acceptance criteria\n\n- `just cargo-build` without arguments produces a usage error, not a reference to pnt-cli\n- `just cargo-build pnt-cli` works when packages/pnt-cli exists\n- `just list-packages-json` output includes `\"maturin\": true/false` for each package\n- No hardcoded reference to `pnt-cli` as a default value remains in the justfile\n\n## Verification\n\n```bash\njust list-packages-json | jq '.[].maturin'\njust cargo-build 2\u003e\u00261 | grep -q 'error' # should fail without argument\n```\n\n\u003c!-- stigmergic-signals --\u003e\n| Signal | Value | Updated |\n|---|---|---|\n| schema-version | 1 | 2026-02-18 |\n| cynefin | clear | 2026-02-18 |\n| surprise | 0.0 | 2026-02-18 |\n| progress | not-started | 2026-02-18 |\n| escalation | none | -- |\n| planning-depth | deep | 2026-02-18 |\n\u003c!-- /stigmergic-signals --\u003e","status":"open","priority":2,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-18T11:52:38.288741-05:00","created_by":"Cameron Smith","updated_at":"2026-02-18T11:52:38.288741-05:00","dependencies":[{"issue_id":"pnt-kaf.4","depends_on_id":"pnt-kaf","type":"parent-child","created_at":"2026-02-18T11:52:38.290369-05:00","created_by":"Cameron Smith"}]} -{"id":"pnt-kaf.5","title":"Add is_maturin field to CI discover-packages step","description":"Add Cargo.toml presence detection to the CI discover-packages step in `.github/workflows/ci.yaml` so the CI matrix annotates which packages are maturin/pyo3 packages.\n\n## Scope\n\n- In the discover-packages step (ci.yaml lines 182-206), add a `Cargo.toml` probe:\n ```bash\n is_maturin=\"false\"\n if [ -f \"\\$d/Cargo.toml\" ]; then is_maturin=\"true\"; fi\n ```\n- Include `is_maturin` in the jq output object for each package\n- Downstream CI jobs can use this field to conditionally run Rust checks, skip maturin packages from uv-build, etc.\n- Verify that `package-release.yaml` detect-maturin step (lines 215-243) remains consistent with the new field\n\n## Acceptance criteria\n\n- CI discover-packages output includes `is_maturin` boolean per package\n- The field is `true` for packages with `Cargo.toml`, `false` otherwise\n- No CI behavioral change for existing workflows (the field is additive information)\n\n## Verification\n\nRun the discover-packages logic locally:\n```bash\nfind packages -maxdepth 2 -name pyproject.toml -path '*/*/pyproject.toml' | sort | while read -r f; do\n d=$(dirname \"$f\"); n=$(basename \"$d\")\n is_maturin=\"false\"\n [ -f \"$d/Cargo.toml\" ] \u0026\u0026 is_maturin=\"true\"\n printf '{\"name\":\"%s\",\"is_maturin\":%s}\\n' \"$n\" \"$is_maturin\"\ndone | jq -sc '.'\n```\n\n\u003c!-- stigmergic-signals --\u003e\n| Signal | Value | Updated |\n|---|---|---|\n| schema-version | 1 | 2026-02-18 |\n| cynefin | clear | 2026-02-18 |\n| surprise | 0.0 | 2026-02-18 |\n| progress | not-started | 2026-02-18 |\n| escalation | none | -- |\n| planning-depth | deep | 2026-02-18 |\n\u003c!-- /stigmergic-signals --\u003e","status":"open","priority":2,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-18T11:52:52.376497-05:00","created_by":"Cameron Smith","updated_at":"2026-02-18T11:52:52.376497-05:00","dependencies":[{"issue_id":"pnt-kaf.5","depends_on_id":"pnt-kaf","type":"parent-child","created_at":"2026-02-18T11:52:52.377273-05:00","created_by":"Cameron Smith"}]} -{"id":"pnt-kaf.6","title":"Replace hardcoded pnt-cli check conditions in mergify.yml","description":"Replace the hardcoded pnt-cli and pnt-functional check-success/check-skipped conditions in `.github/mergify.yml` (lines 27-34) with a pattern that does not reference specific package names.\n\n## Scope\n\n- Lines 27-28 reference python-nix-template by name\n- Lines 30-31 reference pnt-functional by name\n- Lines 33-34 reference pnt-cli by name\n- Replace with either:\n a) A single wildcard regex matching all test-python checks: \\`check-success~=^test-python \\\\\\\\(.*\\`\n b) A dynamically generated mergify config (more complex, future consideration)\n c) Accept that mergify is manually maintained when packages change (pragmatic, acceptable for low-churn repos)\n- Evaluate which approach fits the project's merge queue requirements\n- The base package (python-nix-template placeholder) will always exist, so at minimum one concrete check can remain\n\n## Acceptance criteria\n\n- Mergify conditions do not reference pnt-cli or pnt-functional by name\n- PRs can still merge when all required checks pass\n- Missing packages (due to template toggle) do not block the merge queue\n\n## Verification\n\nReview mergify config syntax and test with a PR after changes are merged.\n\n\u003c!-- stigmergic-signals --\u003e\n| Signal | Value | Updated |\n|---|---|---|\n| schema-version | 1 | 2026-02-18 |\n| cynefin | complicated | 2026-02-18 |\n| surprise | 0.0 | 2026-02-18 |\n| progress | not-started | 2026-02-18 |\n| escalation | none | -- |\n| planning-depth | medium | 2026-02-18 |\n\u003c!-- /stigmergic-signals --\u003e","status":"open","priority":2,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-18T11:53:06.147195-05:00","created_by":"Cameron Smith","updated_at":"2026-02-18T11:53:06.147195-05:00","dependencies":[{"issue_id":"pnt-kaf.6","depends_on_id":"pnt-kaf","type":"parent-child","created_at":"2026-02-18T11:53:06.147883-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-kaf.6","depends_on_id":"pnt-kaf.5","type":"blocks","created_at":"2026-02-18T11:53:31.740448-05:00","created_by":"Cameron Smith"}]} -{"id":"pnt-kaf.7","title":"Verify template instantiation with generalized discovery","description":"End-to-end verification that the generalized package discovery works correctly across both template variants and the post-instantiation pyo3 addition workflow.\n\n## Scope\n\n- Instantiate template with monorepo: true, pyo3: true — verify all packages discovered, Nix builds succeed, CI matrix correct\n- Instantiate template with monorepo: true, pyo3: false — verify pure Python packages discovered, no maturin-related errors, Rust tooling absent from devshell\n- Instantiate with pyo3: false, then manually add a pyo3 package directory with Cargo.toml and nix/packages/{name}/default.nix — verify infrastructure activates\n- Confirm template test in template.nix still passes\n- Verify flake inputs (crane, crane-maturin) are harmless when no maturin packages exist\n\n## Acceptance criteria\n\n- Both template variants instantiate without errors\n- \\`nix flake check\\` passes for both variants\n- \\`just test-all\\` passes for both variants\n- Post-instantiation pyo3 addition is detected by the discovery mechanism\n- Template omnix test passes: \\`nix flake check\\` (which runs template.nix tests)\n\n## Verification\n\n```bash\n# Full template test\nnix flake check --no-build\n\n# Or targeted template verification\njust template-verify\n```\n\n\u003c!-- stigmergic-signals --\u003e\n| Signal | Value | Updated |\n|---|---|---|\n| schema-version | 1 | 2026-02-18 |\n| cynefin | complicated | 2026-02-18 |\n| surprise | 0.0 | 2026-02-18 |\n| progress | not-started | 2026-02-18 |\n| escalation | none | -- |\n| planning-depth | medium | 2026-02-18 |\n\u003c!-- /stigmergic-signals --\u003e","status":"open","priority":2,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-18T11:53:21.519266-05:00","created_by":"Cameron Smith","updated_at":"2026-02-18T11:53:21.519266-05:00","dependencies":[{"issue_id":"pnt-kaf.7","depends_on_id":"pnt-kaf","type":"parent-child","created_at":"2026-02-18T11:53:21.521466-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-kaf.7","depends_on_id":"pnt-kaf.1","type":"blocks","created_at":"2026-02-18T11:53:31.900176-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-kaf.7","depends_on_id":"pnt-kaf.2","type":"blocks","created_at":"2026-02-18T11:53:32.062964-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-kaf.7","depends_on_id":"pnt-kaf.3","type":"blocks","created_at":"2026-02-18T11:53:32.219856-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-kaf.7","depends_on_id":"pnt-kaf.4","type":"blocks","created_at":"2026-02-18T11:53:32.382676-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-kaf.7","depends_on_id":"pnt-kaf.5","type":"blocks","created_at":"2026-02-18T11:53:32.556841-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-kaf.7","depends_on_id":"pnt-kaf.6","type":"blocks","created_at":"2026-02-18T11:53:32.720394-05:00","created_by":"Cameron Smith"}]} +{"id":"pnt-kaf","title":"Generalize package discovery to eliminate hardcoded names","description":"Replace all hardcoded package names (pnt-cli, pnt-functional, python-nix-template) across Nix modules, justfile, CI workflows, and mergify with dynamic discovery based on builtins.readDir and Cargo.toml presence detection.\n\n## Design\n\nThe canonical marker for maturin/pyo3 packages is `packages/{name}/Cargo.toml`. All layers discover packages dynamically:\n\n- Nix modules enumerate `packages/` via `builtins.readDir`, classify by `Cargo.toml` presence\n- Justfile recipes lose hardcoded defaults, `list-packages-json` annotates maturin status\n- CI `discover-packages` step adds `is_maturin` field to matrix output\n- Mergify conditions no longer reference specific package names\n- Convention: each maturin package requires `nix/packages/{name}/default.nix` returning `{ overlay, checks }`\n- Flake inputs (crane, crane-maturin) remain always present regardless of template toggle\n\n## Relationship to pnt-wl6\n\nThis epic may supersede some pnt-wl6 children (wl6.8, wl6.12, wl6.13, wl6.3). Reassess pnt-wl6 after this epic completes.\n\n\u003c!-- stigmergic-signals --\u003e\n| Signal | Value | Updated |\n|---|---|---|\n| schema-version | 1 | 2026-02-18 |\n| cynefin | complicated | 2026-02-18 |\n| surprise | 0.0 | 2026-02-18 |\n| progress | not-started | 2026-02-18 |\n| escalation | none | -- |\n| planning-depth | medium | 2026-02-18 |\n\u003c!-- /stigmergic-signals --\u003e","notes":"\u003c!-- checkpoint-context --\u003e\n## State estimate (2026-02-18)\n\nWhat was done: All 7 children implemented and verified. PR #92 merged to main after CI passed (CI/CD + Template workflows). Epic branch pnt-kaf-generalized-discovery contains 12 implementation commits replacing hardcoded package names across python.nix, devshell.nix, containers.nix, justfile, ci.yaml, and mergify.yml with builtins.readDir-based discovery and Cargo.toml classification.\n\nWhat was learned: The containers.nix module replicates discovery logic at module level rather than consuming _module.args because flake.containerMatrix operates outside perSystem scope. This is a known architectural constraint of flake-parts module composition.\n\nWhat remains: Epic is complete and eligible for closure. However, a follow-up workstream was identified: the template substitution mechanism in modules/template.nix conflates repository name with primary package name. When instantiated with a different repo name than package name (e.g. sciexp/data hosting package omicsio), 14 post-instantiation fixes are needed. Evidence exists at ~/projects/sciexp/data (init branch). Next session should examine those fix commits, classify the substitution contexts (repo-name, platform-identity, template-reference), and design a repo-name parameter with backward-compatible default. This may extend pnt-kaf or become a new epic depending on scope assessment.\n\nDownstream impact: The new _module.args exports (packageNames, maturinPackageNames, purePackageNames, hasMaturinPackages) are consumed by devshell.nix and containers.nix. Any future Nix modules needing package classification should consume these args rather than reimplementing discovery.\n\u003c!-- /checkpoint-context --\u003e","status":"inreview","priority":2,"issue_type":"epic","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-18T11:51:28.720914-05:00","created_by":"Cameron Smith","updated_at":"2026-02-18T13:52:04.516504-05:00"} +{"id":"pnt-kaf.1","title":"Replace hardcoded package enumeration in python.nix with readDir discovery","description":"Core Nix module change. Replace all hardcoded `builtins.pathExists ../packages/pnt-cli` and `builtins.pathExists ../packages/pnt-functional` declarations in `modules/python.nix` with dynamic discovery using `builtins.readDir ../packages`.\n\n## Scope\n\n- Add package discovery logic: enumerate `packages/` via `builtins.readDir`, filter directories, classify as maturin (`Cargo.toml` present) vs pure Python\n- Generalize `packageWorkspaces`: iterate discovered package names through `loadPackage` instead of hardcoding three entries (lines 45-53)\n- Generalize `mkPackageModule`: iterate `maturinPackageNames`, import `nix/packages/{name}/default.nix` for each, compose overlays and checks (lines 65-74)\n- Generalize `mkPythonSet` overlay composition: replace `lib.optional hasCli packageWorkspaces.pnt-cli.overlay` pattern with iteration over discovered packages (lines 91-104)\n- Generalize `mkEditablePythonSet`: iterate `purePackageNames` only (maturin packages excluded per existing convention at line 107-110)\n- Generalize `rustChecks` collection: iterate all maturin package modules (lines 146-150)\n- Expose `hasMaturinPackages`, `maturinPackageNames`, `purePackageNames` via `_module.args` for devshell.nix and containers.nix\n- Add eval-time error when `Cargo.toml` exists but `nix/packages/{name}/default.nix` is missing\n- The base package name (currently `python-nix-template`) is the omnix placeholder — it must participate in discovery like any other package, not be hardcoded separately\n\n## Acceptance criteria\n\n- `nix eval .#packages.x86_64-linux --json | jq 'keys'` succeeds and includes all existing package outputs\n- `nix eval .#checks.x86_64-linux --json | jq 'keys'` includes pnt-cli checks when packages/pnt-cli exists\n- `nix build .#packages.x86_64-linux.default` succeeds\n- Adding a new pure Python package to `packages/` is picked up without editing python.nix\n- Removing `packages/pnt-cli` causes maturin-related overlays and checks to disappear without errors\n\n## Verification\n\n```bash\nnix flake check --no-build\nnix eval .#packages.x86_64-darwin --json | jq 'keys'\nnix eval .#checks.x86_64-darwin --json | jq 'keys'\n```\n\n\u003c!-- stigmergic-signals --\u003e\n| Signal | Value | Updated |\n|---|---|---|\n| schema-version | 1 | 2026-02-18 |\n| cynefin | complicated | 2026-02-18 |\n| surprise | 0.0 | 2026-02-18 |\n| progress | not-started | 2026-02-18 |\n| escalation | none | -- |\n| planning-depth | medium | 2026-02-18 |\n\u003c!-- /stigmergic-signals --\u003e","status":"closed","priority":2,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-18T11:52:02.76287-05:00","created_by":"Cameron Smith","updated_at":"2026-02-18T12:19:58.165915-05:00","closed_at":"2026-02-18T12:19:58.165915-05:00","close_reason":"Implemented in f03617b","dependencies":[{"issue_id":"pnt-kaf.1","depends_on_id":"pnt-kaf","type":"parent-child","created_at":"2026-02-18T11:52:02.7641-05:00","created_by":"Cameron Smith"}]} +{"id":"pnt-kaf.2","title":"Replace hardcoded hasCli in devshell.nix with discovery-based detection","description":"Replace `hasCli = builtins.pathExists ../packages/pnt-cli` in `modules/devshell.nix` (line 18) with `hasMaturinPackages` consumed from `_module.args` as exposed by the generalized python.nix.\n\n## Scope\n\n- Remove local `hasCli` declaration\n- Consume `hasMaturinPackages` from `_module.args` (provided by pnt-kaf.1)\n- Replace `lib.optionals hasCli` (line 58) with `lib.optionals hasMaturinPackages`\n- Update comment on line 61 from \"Rust tooling for pnt-cli pyo3 extension\" to generic \"Rust tooling for maturin/pyo3 packages\"\n\n## Acceptance criteria\n\n- `nix develop` enters devshell successfully\n- When `packages/pnt-cli` exists: cargo, rustc, clippy, cargo-nextest, maturin are on PATH\n- When no maturin packages exist: Rust tools absent from PATH\n- No hardcoded reference to `pnt-cli` remains in devshell.nix\n\n## Verification\n\n```bash\nnix develop -c which cargo # should succeed when maturin packages exist\nnix develop -c which maturin\n```\n\n\u003c!-- stigmergic-signals --\u003e\n| Signal | Value | Updated |\n|---|---|---|\n| schema-version | 1 | 2026-02-18 |\n| cynefin | complicated | 2026-02-18 |\n| surprise | 0.0 | 2026-02-18 |\n| progress | not-started | 2026-02-18 |\n| escalation | none | -- |\n| planning-depth | medium | 2026-02-18 |\n\u003c!-- /stigmergic-signals --\u003e","status":"closed","priority":2,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-18T11:52:14.654722-05:00","created_by":"Cameron Smith","updated_at":"2026-02-18T12:24:44.792724-05:00","closed_at":"2026-02-18T12:24:44.792724-05:00","close_reason":"Implemented in 21a0288","dependencies":[{"issue_id":"pnt-kaf.2","depends_on_id":"pnt-kaf","type":"parent-child","created_at":"2026-02-18T11:52:14.655494-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-kaf.2","depends_on_id":"pnt-kaf.1","type":"blocks","created_at":"2026-02-18T11:53:31.425769-05:00","created_by":"Cameron Smith"}]} +{"id":"pnt-kaf.3","title":"Replace hardcoded container definitions in containers.nix with discovery","description":"Replace the hardcoded `productionContainerDefs` attrset in `modules/containers.nix` (lines 14-20) with a discovery-based mechanism that derives container definitions from per-package metadata rather than naming `pnt-cli` explicitly.\n\n## Scope\n\n- Remove local `hasCli = builtins.pathExists ../packages/pnt-cli` declaration (line 10)\n- Replace hardcoded `productionContainerDefs` with discovery. Two approaches to evaluate:\n a) Per-package `.container.nix` or `.ci.json` metadata declaring entrypoint and description\n b) Derive from `[project.scripts]` in `pyproject.toml` via Nix evaluation\n Option (a) aligns with existing `.ci.json` convention used in CI discover-packages.\n- Consume `maturinPackageNames` or similar from `_module.args` if container definitions should be restricted to maturin packages, or generalize to allow any package to declare a container\n- `containerMatrix` flake output should reflect dynamically discovered containers\n\n## Acceptance criteria\n\n- `nix eval .#containerMatrix --json` reflects discovered packages\n- No hardcoded reference to `pnt-cli` remains in containers.nix\n- When `packages/pnt-cli` exists with container metadata: container appears in matrix\n- When no container-capable packages exist: empty matrix, no errors\n\n## Verification\n\n```bash\nnix eval .#containerMatrix --json | jq .\n```\n\n\u003c!-- stigmergic-signals --\u003e\n| Signal | Value | Updated |\n|---|---|---|\n| schema-version | 1 | 2026-02-18 |\n| cynefin | complicated | 2026-02-18 |\n| surprise | 0.0 | 2026-02-18 |\n| progress | not-started | 2026-02-18 |\n| escalation | none | -- |\n| planning-depth | medium | 2026-02-18 |\n\u003c!-- /stigmergic-signals --\u003e","status":"closed","priority":2,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-18T11:52:26.815361-05:00","created_by":"Cameron Smith","updated_at":"2026-02-18T12:24:44.965436-05:00","closed_at":"2026-02-18T12:24:44.965436-05:00","close_reason":"Implemented in 5292a0a","dependencies":[{"issue_id":"pnt-kaf.3","depends_on_id":"pnt-kaf","type":"parent-child","created_at":"2026-02-18T11:52:26.816289-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-kaf.3","depends_on_id":"pnt-kaf.1","type":"blocks","created_at":"2026-02-18T11:53:31.581192-05:00","created_by":"Cameron Smith"}]} +{"id":"pnt-kaf.4","title":"Remove hardcoded pnt-cli defaults from justfile Rust and container recipes","description":"Remove hardcoded `pnt-cli` default values from justfile Rust and container recipe parameters. Extend `list-packages-json` to annotate maturin status.\n\n## Scope\n\n- Remove `=\"pnt-cli\"` defaults from: cargo-build, cargo-test, cargo-clippy, cargo-nextest, cargo-check (lines 409-430)\n- Remove `CONTAINER=\"pnt-cli\"` defaults from: container-build-production, container-load-production, container-push-production (lines ~296-307)\n- Rust and container recipes become required-parameter recipes (invoker must specify package name)\n- Extend `list-packages-json` recipe to emit `maturin` boolean field per package based on `Cargo.toml` presence\n- Consider adding a `list-maturin-packages` convenience recipe that filters to maturin-only packages\n\n## Acceptance criteria\n\n- `just cargo-build` without arguments produces a usage error, not a reference to pnt-cli\n- `just cargo-build pnt-cli` works when packages/pnt-cli exists\n- `just list-packages-json` output includes `\"maturin\": true/false` for each package\n- No hardcoded reference to `pnt-cli` as a default value remains in the justfile\n\n## Verification\n\n```bash\njust list-packages-json | jq '.[].maturin'\njust cargo-build 2\u003e\u00261 | grep -q 'error' # should fail without argument\n```\n\n\u003c!-- stigmergic-signals --\u003e\n| Signal | Value | Updated |\n|---|---|---|\n| schema-version | 1 | 2026-02-18 |\n| cynefin | clear | 2026-02-18 |\n| surprise | 0.0 | 2026-02-18 |\n| progress | not-started | 2026-02-18 |\n| escalation | none | -- |\n| planning-depth | deep | 2026-02-18 |\n\u003c!-- /stigmergic-signals --\u003e","status":"closed","priority":2,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-18T11:52:38.288741-05:00","created_by":"Cameron Smith","updated_at":"2026-02-18T12:19:58.356577-05:00","closed_at":"2026-02-18T12:19:58.356577-05:00","close_reason":"Implemented in d64722e","dependencies":[{"issue_id":"pnt-kaf.4","depends_on_id":"pnt-kaf","type":"parent-child","created_at":"2026-02-18T11:52:38.290369-05:00","created_by":"Cameron Smith"}]} +{"id":"pnt-kaf.5","title":"Add is_maturin field to CI discover-packages step","description":"Add Cargo.toml presence detection to the CI discover-packages step in `.github/workflows/ci.yaml` so the CI matrix annotates which packages are maturin/pyo3 packages.\n\n## Scope\n\n- In the discover-packages step (ci.yaml lines 182-206), add a `Cargo.toml` probe:\n ```bash\n is_maturin=\"false\"\n if [ -f \"\\$d/Cargo.toml\" ]; then is_maturin=\"true\"; fi\n ```\n- Include `is_maturin` in the jq output object for each package\n- Downstream CI jobs can use this field to conditionally run Rust checks, skip maturin packages from uv-build, etc.\n- Verify that `package-release.yaml` detect-maturin step (lines 215-243) remains consistent with the new field\n\n## Acceptance criteria\n\n- CI discover-packages output includes `is_maturin` boolean per package\n- The field is `true` for packages with `Cargo.toml`, `false` otherwise\n- No CI behavioral change for existing workflows (the field is additive information)\n\n## Verification\n\nRun the discover-packages logic locally:\n```bash\nfind packages -maxdepth 2 -name pyproject.toml -path '*/*/pyproject.toml' | sort | while read -r f; do\n d=$(dirname \"$f\"); n=$(basename \"$d\")\n is_maturin=\"false\"\n [ -f \"$d/Cargo.toml\" ] \u0026\u0026 is_maturin=\"true\"\n printf '{\"name\":\"%s\",\"is_maturin\":%s}\\n' \"$n\" \"$is_maturin\"\ndone | jq -sc '.'\n```\n\n\u003c!-- stigmergic-signals --\u003e\n| Signal | Value | Updated |\n|---|---|---|\n| schema-version | 1 | 2026-02-18 |\n| cynefin | clear | 2026-02-18 |\n| surprise | 0.0 | 2026-02-18 |\n| progress | not-started | 2026-02-18 |\n| escalation | none | -- |\n| planning-depth | deep | 2026-02-18 |\n\u003c!-- /stigmergic-signals --\u003e","status":"closed","priority":2,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-18T11:52:52.376497-05:00","created_by":"Cameron Smith","updated_at":"2026-02-18T12:19:58.543923-05:00","closed_at":"2026-02-18T12:19:58.543923-05:00","close_reason":"Implemented in b5f4df4","dependencies":[{"issue_id":"pnt-kaf.5","depends_on_id":"pnt-kaf","type":"parent-child","created_at":"2026-02-18T11:52:52.377273-05:00","created_by":"Cameron Smith"}]} +{"id":"pnt-kaf.6","title":"Replace hardcoded pnt-cli check conditions in mergify.yml","description":"Replace the hardcoded pnt-cli and pnt-functional check-success/check-skipped conditions in `.github/mergify.yml` (lines 27-34) with a pattern that does not reference specific package names.\n\n## Scope\n\n- Lines 27-28 reference python-nix-template by name\n- Lines 30-31 reference pnt-functional by name\n- Lines 33-34 reference pnt-cli by name\n- Replace with either:\n a) A single wildcard regex matching all test-python checks: \\`check-success~=^test-python \\\\\\\\(.*\\`\n b) A dynamically generated mergify config (more complex, future consideration)\n c) Accept that mergify is manually maintained when packages change (pragmatic, acceptable for low-churn repos)\n- Evaluate which approach fits the project's merge queue requirements\n- The base package (python-nix-template placeholder) will always exist, so at minimum one concrete check can remain\n\n## Acceptance criteria\n\n- Mergify conditions do not reference pnt-cli or pnt-functional by name\n- PRs can still merge when all required checks pass\n- Missing packages (due to template toggle) do not block the merge queue\n\n## Verification\n\nReview mergify config syntax and test with a PR after changes are merged.\n\n\u003c!-- stigmergic-signals --\u003e\n| Signal | Value | Updated |\n|---|---|---|\n| schema-version | 1 | 2026-02-18 |\n| cynefin | complicated | 2026-02-18 |\n| surprise | 0.0 | 2026-02-18 |\n| progress | not-started | 2026-02-18 |\n| escalation | none | -- |\n| planning-depth | medium | 2026-02-18 |\n\u003c!-- /stigmergic-signals --\u003e","status":"closed","priority":2,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-18T11:53:06.147195-05:00","created_by":"Cameron Smith","updated_at":"2026-02-18T12:24:45.125678-05:00","closed_at":"2026-02-18T12:24:45.125678-05:00","close_reason":"Implemented in 9a4d8bb","dependencies":[{"issue_id":"pnt-kaf.6","depends_on_id":"pnt-kaf","type":"parent-child","created_at":"2026-02-18T11:53:06.147883-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-kaf.6","depends_on_id":"pnt-kaf.5","type":"blocks","created_at":"2026-02-18T11:53:31.740448-05:00","created_by":"Cameron Smith"}]} +{"id":"pnt-kaf.7","title":"Verify template instantiation with generalized discovery","description":"End-to-end verification that the generalized package discovery works correctly across both template variants and the post-instantiation pyo3 addition workflow.\n\n## Scope\n\n- Instantiate template with monorepo: true, pyo3: true — verify all packages discovered, Nix builds succeed, CI matrix correct\n- Instantiate template with monorepo: true, pyo3: false — verify pure Python packages discovered, no maturin-related errors, Rust tooling absent from devshell\n- Instantiate with pyo3: false, then manually add a pyo3 package directory with Cargo.toml and nix/packages/{name}/default.nix — verify infrastructure activates\n- Confirm template test in template.nix still passes\n- Verify flake inputs (crane, crane-maturin) are harmless when no maturin packages exist\n\n## Acceptance criteria\n\n- Both template variants instantiate without errors\n- \\`nix flake check\\` passes for both variants\n- \\`just test-all\\` passes for both variants\n- Post-instantiation pyo3 addition is detected by the discovery mechanism\n- Template omnix test passes: \\`nix flake check\\` (which runs template.nix tests)\n\n## Verification\n\n```bash\n# Full template test\nnix flake check --no-build\n\n# Or targeted template verification\njust template-verify\n```\n\n\u003c!-- stigmergic-signals --\u003e\n| Signal | Value | Updated |\n|---|---|---|\n| schema-version | 1 | 2026-02-18 |\n| cynefin | complicated | 2026-02-18 |\n| surprise | 0.0 | 2026-02-18 |\n| progress | not-started | 2026-02-18 |\n| escalation | none | -- |\n| planning-depth | medium | 2026-02-18 |\n\u003c!-- /stigmergic-signals --\u003e","status":"closed","priority":2,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-18T11:53:21.519266-05:00","created_by":"Cameron Smith","updated_at":"2026-02-18T12:30:23.648642-05:00","closed_at":"2026-02-18T12:30:23.648642-05:00","close_reason":"Verified in f705185 — all nix eval, justfile, CI, and residual-reference checks pass","dependencies":[{"issue_id":"pnt-kaf.7","depends_on_id":"pnt-kaf","type":"parent-child","created_at":"2026-02-18T11:53:21.521466-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-kaf.7","depends_on_id":"pnt-kaf.1","type":"blocks","created_at":"2026-02-18T11:53:31.900176-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-kaf.7","depends_on_id":"pnt-kaf.2","type":"blocks","created_at":"2026-02-18T11:53:32.062964-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-kaf.7","depends_on_id":"pnt-kaf.3","type":"blocks","created_at":"2026-02-18T11:53:32.219856-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-kaf.7","depends_on_id":"pnt-kaf.4","type":"blocks","created_at":"2026-02-18T11:53:32.382676-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-kaf.7","depends_on_id":"pnt-kaf.5","type":"blocks","created_at":"2026-02-18T11:53:32.556841-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-kaf.7","depends_on_id":"pnt-kaf.6","type":"blocks","created_at":"2026-02-18T11:53:32.720394-05:00","created_by":"Cameron Smith"}]} {"id":"pnt-m1w","title":"Fix template.yaml workflow for proper PyO3 and multi-variant validation","description":"Align template.yaml with typescript-nix-template patterns and fix the test-omnix-template job.\n\nRoot cause resolved (pnt-3qp): The PyO3 _native import failure was caused by pytest collecting tests from the source namespace, shadowing the installed wheel. Fixed by relocating pnt-cli tests to tests/ (outside src/). The editable overlay root was also fixed to use per-package paths ($REPO_ROOT/packages/\u003cname\u003e).\n\nRemaining work:\n- Replace nix develop -c pytest with nix flake check --accept-flake-config in template test\n- nix flake check exercises all Nix builds including maturin wheel, validating PyO3 integration\n- Add set-variables job (debug flags, skip-ci, checkout details)\n- Test TWO variants matching monorepo-package boolean parameter:\n 1. Full monorepo (monorepo-package=true): includes pnt-functional + pnt-cli\n 2. Single package (monorepo-package=false): main package only\n- Fix cachix reference: instantiated template tries pnt-mono.cachix.org which 401s (expected for fresh project, but should be parameterized or removed)\n- Consider adding lightweight smoke test after flake check: nix develop -c python -c 'import pnt_mono'\n- Root pyproject.toml has been removed; template assertion now checks ruff.toml\n- Per-package justfile recipes are now the standard pattern (test, lint, type take package parameter)\n\nReference: typescript-nix-template .github/workflows/template.yaml tests full and minimal variants with git init, bun install, nix flake check sequence.","status":"closed","priority":2,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-03T10:10:08.916223-05:00","created_by":"Cameron Smith","updated_at":"2026-02-03T17:47:13.700141-05:00","closed_at":"2026-02-03T17:47:13.700141-05:00","close_reason":"Implemented in 3a0873e. Template workflow single-package variant fixed via builtins.pathExists guard on pnt-functional references in modules/python.nix. Both Template and CI/CD workflows passing.","dependencies":[{"issue_id":"pnt-m1w","depends_on_id":"pnt-edl","type":"child-of","created_at":"2026-02-03T10:10:17.838035-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-m1w","depends_on_id":"pnt-j6f","type":"blocked-by","created_at":"2026-02-03T10:10:18.005313-05:00","created_by":"Cameron Smith"},{"issue_id":"pnt-m1w","depends_on_id":"pnt-3qp","type":"blocked-by","created_at":"2026-02-03T10:20:21.254918-05:00","created_by":"Cameron Smith"}],"comments":[{"id":7,"issue_id":"pnt-m1w","author":"Cameron Smith","text":"Checkpoint: 2026-02-03 session\n\nDone:\n- Rewrote template.yaml aligned with typescript-nix-template pattern\n- Added set-variables job, cached-ci-job integration, workflow-level concurrency, force_run input\n- Replaced nix develop -c pytest with nix flake check + import smoke tests\n- Fixed per-package uv lock (federation model has no root pyproject.toml)\n- Disabled dev containers (catppuccin-starship build failure via nixpod) to unblock nix flake check\n- Archived dev container code to nix/disabled/containers-dev.nix.txt\n- Removed nixpod flake input and dev container justfile recipes\n\nRemaining:\n- CI run 21646259884 is in progress, needs to pass\n- If CI passes: close pnt-m1w and pnt-edl\n- If CI fails: debug and iterate\n\nCommits this session: 5eaab99..ee0295d (7 commits)","created_at":"2026-02-03T20:19:44Z"}]} {"id":"pnt-m3t","title":"Adopt setup-nix composite action with nothing-but-nix pattern","description":"Port the vanixiets setup-nix composite action to python-nix-template, replacing inline Nix installation across all workflows.\n\nCurrent state:\n- ci.yaml, python-test.yaml, build-nix-images.yaml each inline DeterminateSystems/nix-installer-action\n- build-nix-images.yaml uses ad-hoc maximize-build-space shell script\n- scripts/ci/maximize-build-space.sh exists but is not wired into workflows\n\nTarget state (matching vanixiets):\n- .github/actions/setup-nix/action.yml composite action\n- Uses wimpysworld/nothing-but-nix for space reclamation (replaces maximize-build-space.sh)\n- Uses cachix/install-nix-action with pinned Nix version\n- Configures build-dir = /nix/build (nothing-but-nix workaround)\n- Integrates magic-nix-cache and cachix setup\n- Hatchet protocol support for configurable space levels\n- All workflows delegate Nix setup to this single action\n\nAcceptance criteria:\n- .github/actions/setup-nix/action.yml implemented (port from vanixiets)\n- ci.yaml nixci job uses setup-nix action\n- python-test.yaml uses setup-nix action\n- build-nix-images.yaml uses setup-nix action (replaces inline maximize-build-space)\n- .github/actions/build-nix-image/ updated (its Nix install steps replaced by setup-nix)\n- scripts/ci/maximize-build-space.sh removed (superseded by nothing-but-nix)\n- setup-python-uv action evaluated for removal (if all Python CI runs via nix develop)\n\nReference: ~/projects/nix-workspace/vanixiets/.github/actions/setup-nix/action.yml","status":"closed","priority":2,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-02T20:24:06.14337-05:00","created_by":"Cameron Smith","updated_at":"2026-02-02T21:16:20.627824-05:00","closed_at":"2026-02-02T21:16:20.627824-05:00","close_reason":"Implemented in 0b1e4fa, setup-nix action ported from vanixiets"} {"id":"pnt-m7j","title":"Sync mergify.yml with vanixiets/typescript-nix-template patterns","description":"Upgrade mergify.yml from basic 26-line config to full-featured ~120-line config matching vanixiets and typescript-nix-template patterns.\n\nReference files:\n- ~/projects/nix-workspace/vanixiets/.github/mergify.yml\n- ~/projects/nix-workspace/typescript-nix-template/.github/mergify.yml\n\nChanges needed:\n- Add YAML anchors for base_conditions, required_checks, human_author_conditions, bot_author_conditions\n- Expand required_checks to cover all CI jobs (secrets-scan, set-variables, check-fast-forward, flake-validation, bootstrap-verification, etc.)\n- Split pull_request_rules into separate rules for human vs bot authors\n- Create separate queue rules: default for humans (batch_size: 1), bot-updates for bots (batch_size: 10)\n- Update checks_timeout format to match vanixiets pattern\n\nScope: ~2 hours","status":"closed","priority":1,"issue_type":"task","owner":"cameron.ray.smith@gmail.com","created_at":"2026-02-05T13:47:53.370062-05:00","created_by":"Cameron Smith","updated_at":"2026-02-05T15:07:28.214895-05:00","closed_at":"2026-02-05T15:07:28.214895-05:00","close_reason":"Implemented in f369393"} diff --git a/.github/mergify.yml b/.github/mergify.yml index e041b87..43c311a 100644 --- a/.github/mergify.yml +++ b/.github/mergify.yml @@ -24,14 +24,8 @@ shared: - check-success=nix (x86_64-linux, ubuntu-latest, devshells) - check-skipped=nix (x86_64-linux, ubuntu-latest, devshells) - or: - - "check-success~=^test-python \\(python-nix-template, packages/python-nix-template.* / test$" - - "check-skipped~=^test-python \\(python-nix-template, packages/python-nix-template.* / test$" - - or: - - "check-success~=^test-python \\(pnt-functional, packages/pnt-functional.* / test$" - - "check-skipped~=^test-python \\(pnt-functional, packages/pnt-functional.* / test$" - - or: - - "check-success~=^test-python \\(pnt-cli, packages/pnt-cli.* / test$" - - "check-skipped~=^test-python \\(pnt-cli, packages/pnt-cli.* / test$" + - "check-success~=^test-python \\(.* / test$" + - "check-skipped~=^test-python \\(.* / test$" - or: - check-success=test-omnix-template - check-skipped=test-omnix-template diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b908e6c..9a240db 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -186,6 +186,10 @@ jobs: d=$(dirname "$f") n=$(basename "$d") + # Detect maturin/pyo3 package by Cargo.toml presence + is_maturin="false" + if [ -f "$d/Cargo.toml" ]; then is_maturin="true"; fi + # Read per-package CI metadata from .ci.json if present ci_json="$d/.ci.json" if [ -f "$ci_json" ]; then @@ -199,9 +203,10 @@ jobs: jq -nc \ --arg name "$n" \ --arg path "$d" \ + --argjson is_maturin "$is_maturin" \ --arg build_images "$build_images" \ --argjson images "$images" \ - '{name: $name, path: $path, "build-images": $build_images, images: ($images | tojson)}' + '{name: $name, path: $path, "is_maturin": $is_maturin, "build-images": $build_images, images: ($images | tojson)}' done | jq -sc '.') echo "packages=$PACKAGES" >> "$GITHUB_OUTPUT" echo "Discovered packages: $PACKAGES" diff --git a/justfile b/justfile index 14813a4..6508baa 100644 --- a/justfile +++ b/justfile @@ -212,9 +212,15 @@ list-packages-json: @ls -d packages/*/pyproject.toml | while read f; do \ d=$(dirname "$f"); \ n=$(basename "$d"); \ - printf '{"name":"%s","path":"%s"}\n' "$n" "$d"; \ + if [ -f "$d/Cargo.toml" ]; then m=true; else m=false; fi; \ + printf '{"name":"%s","path":"%s","maturin":%s}\n' "$n" "$d" "$m"; \ done | jq -sc '.' +# Discover maturin (Rust/pyo3) packages as JSON array +[group('CI/CD')] +list-maturin-packages: + @just list-packages-json | jq '[.[] | select(.maturin)]' + # Sync dependencies for a package via uv [group('CI/CD')] ci-sync package: @@ -293,17 +299,17 @@ conda-check package="python-nix-template": (conda-lint package) (conda-type pack # Build production container image [group('containers')] -container-build-production CONTAINER="pnt-cli": +container-build-production CONTAINER: nix build ".#{{CONTAINER}}ProductionImage" -L # Load production container to local Docker daemon [group('containers')] -container-load-production CONTAINER="pnt-cli": +container-load-production CONTAINER: nix run ".#{{CONTAINER}}ProductionImage.copyToDockerDaemon" # Push production container manifest (requires registry auth) [group('containers')] -container-push-production CONTAINER="pnt-cli" VERSION="0.0.0" +TAGS="": +container-push-production CONTAINER VERSION="0.0.0" +TAGS="": VERSION={{VERSION}} TAGS={{TAGS}} nix run --impure ".#{{CONTAINER}}Manifest" -L # Display container CI matrix @@ -406,27 +412,27 @@ check package="python-nix-template": (lint package) (type package) (test package # Build Rust crates for a package [group('rust')] -cargo-build package="pnt-cli": +cargo-build package: cd packages/{{package}}/crates && cargo build # Run Rust tests via cargo test [group('rust')] -cargo-test package="pnt-cli": +cargo-test package: cd packages/{{package}}/crates && cargo test # Run Rust clippy lints [group('rust')] -cargo-clippy package="pnt-cli": +cargo-clippy package: cd packages/{{package}}/crates && cargo clippy --all-targets -- --deny warnings # Run Rust tests via cargo-nextest [group('rust')] -cargo-nextest package="pnt-cli": +cargo-nextest package: cd packages/{{package}}/crates && cargo nextest run --no-tests=pass # Run all Rust checks (clippy, test) [group('rust')] -cargo-check package="pnt-cli": (cargo-clippy package) (cargo-test package) +cargo-check package: (cargo-clippy package) (cargo-test package) @printf "\nAll Rust checks passed for {{package}}.\n" ## Secrets diff --git a/modules/containers.nix b/modules/containers.nix index cc01aa5..e5fc1c7 100644 --- a/modules/containers.nix +++ b/modules/containers.nix @@ -7,17 +7,23 @@ let gitHubOrg = "sciexp"; repoName = "python-nix-template"; - hasCli = builtins.pathExists ../packages/pnt-cli; - - # Production container definitions (nix2container) - # Add new containers here; containerMatrix auto-discovers them. - productionContainerDefs = lib.optionalAttrs hasCli { - pnt-cli = { - name = "pnt-cli"; - entrypoint = "pnt-cli"; - description = "pnt-cli with pyo3 native bindings"; - }; - }; + # Discover maturin packages from packages/ directory. Each subdirectory + # containing a Cargo.toml is a maturin/pyo3 package that gets a production + # container. This mirrors the discovery logic in python.nix. + packageDirs = builtins.readDir ../packages; + packageNames = builtins.filter (name: packageDirs.${name} == "directory") ( + builtins.attrNames packageDirs + ); + isMaturin = name: builtins.pathExists (../packages + "/${name}/Cargo.toml"); + maturinPackageNames = builtins.filter isMaturin packageNames; + + # Production container definitions derived from discovered maturin packages. + # Each maturin package exposes a CLI binary via pyo3 and gets a container. + productionContainerDefs = lib.genAttrs maturinPackageNames (name: { + inherit name; + entrypoint = name; + description = "${name} with pyo3 native bindings"; + }); in { perSystem = diff --git a/modules/devshell.nix b/modules/devshell.nix index b938000..ff70395 100644 --- a/modules/devshell.nix +++ b/modules/devshell.nix @@ -12,11 +12,10 @@ packageWorkspaces, editablePythonSets, pythonVersions, + hasMaturinPackages ? false, ... }: let - hasCli = builtins.pathExists ../packages/pnt-cli; - # Merge deps from all independent package workspaces, unioning extras lists # for shared dependency names rather than silently dropping via // allDeps = lib.foldlAttrs ( @@ -55,10 +54,10 @@ sops ssh-to-age ] - ++ lib.optionals hasCli ( + ++ lib.optionals hasMaturinPackages ( with pkgs; [ - # Rust tooling for pnt-cli pyo3 extension + # Rust tooling for maturin/pyo3 packages cargo rustc clippy diff --git a/modules/python.nix b/modules/python.nix index 0ca2a14..b9e60bf 100644 --- a/modules/python.nix +++ b/modules/python.nix @@ -19,6 +19,20 @@ py313 = pkgs.python313; }; + # Discover packages from packages/ directory. Each subdirectory is a + # Python package. Packages containing Cargo.toml are maturin/pyo3 + # packages requiring a corresponding nix/packages/{name}/default.nix + # module that exports { overlay, checks }. + packageDirs = builtins.readDir ../packages; + packageNames = builtins.filter (name: packageDirs.${name} == "directory") ( + builtins.attrNames packageDirs + ); + + isMaturin = name: builtins.pathExists (../packages + "/${name}/Cargo.toml"); + maturinPackageNames = builtins.filter isMaturin packageNames; + purePackageNames = builtins.filter (name: !(isMaturin name)) packageNames; + hasMaturinPackages = maturinPackageNames != [ ]; + # Load each Python package independently (no root workspace). # Each package directory contains its own pyproject.toml and uv.lock, # resolved independently following the LangChain federation model. @@ -39,39 +53,24 @@ }; }; - hasFunctional = builtins.pathExists ../packages/pnt-functional; - hasCli = builtins.pathExists ../packages/pnt-cli; - - packageWorkspaces = { - python-nix-template = loadPackage "python-nix-template" ../packages/python-nix-template; - } - // lib.optionalAttrs hasCli { - pnt-cli = loadPackage "pnt-cli" ../packages/pnt-cli; - } - // lib.optionalAttrs hasFunctional { - pnt-functional = loadPackage "pnt-functional" ../packages/pnt-functional; - }; - - # Per-package Nix modules with optional Rust overlays. - # Packages with Rust extensions get a dedicated module in nix/packages/ - # that encapsulates crane configuration and exports an overlay + checks. - # When pnt-cli is absent (pyo3-package: false), returns an inert module - # with no-op overlay and empty checks to avoid import path errors. - emptyModule = { - overlay = _final: _prev: { }; - checks = { }; - }; + packageWorkspaces = lib.genAttrs packageNames (name: loadPackage name (../packages + "/${name}")); + # Per-package Nix modules with Rust overlays. Each maturin package + # requires nix/packages/{name}/default.nix exporting { overlay, checks }. + # Eval-time error when Cargo.toml exists but the module is missing. mkPackageModule = - python: - if hasCli then - import ../nix/packages/pnt-cli { + name: python: + let + modulePath = ../nix/packages + "/${name}/default.nix"; + in + if builtins.pathExists modulePath then + import modulePath { inherit pkgs lib python; crane = inputs.crane; inherit (inputs) crane-maturin pyproject-nix; } else - emptyModule; + throw "Maturin package ${name} requires nix/packages/${name}/default.nix"; # Compose per-package uv2nix overlays with shared overrides. # @@ -92,49 +91,38 @@ [ inputs.pyproject-build-systems.overlays.default ] - ++ lib.optional hasCli packageWorkspaces.pnt-cli.overlay - ++ lib.optional hasFunctional packageWorkspaces.pnt-functional.overlay + ++ map (name: packageWorkspaces.${name}.overlay) packageNames + ++ map (name: (mkPackageModule name python).overlay) maturinPackageNames ++ [ - packageWorkspaces.python-nix-template.overlay - # Rust integration overlay for pnt-cli (crane + maturin) - (mkPackageModule python).overlay packageOverrides sdistOverrides ] ) ); - # Editable set excludes pnt-cli: maturin packages are incompatible with - # uv2nix's editable overlay (pyprojectFixupEditableHook expects EDITABLE_ROOT - # which maturin's build process does not set). pnt-cli is built as a regular - # wheel in the devshell; use `maturin develop` for iterative Rust development. + # Editable set excludes maturin packages: maturin packages are + # incompatible with uv2nix's editable overlay (pyprojectFixupEditableHook + # expects EDITABLE_ROOT which maturin's build process does not set). + # Maturin packages are built as regular wheels in the devshell; use + # `maturin develop` for iterative Rust development. mkEditablePythonSet = python: (mkPythonSet python).overrideScope ( lib.composeManyExtensions ( - lib.optional hasFunctional packageWorkspaces.pnt-functional.editableOverlay + map (name: packageWorkspaces.${name}.editableOverlay) purePackageNames ++ [ - packageWorkspaces.python-nix-template.editableOverlay ( final: prev: - { - python-nix-template = prev.python-nix-template.overrideAttrs (old: { - nativeBuildInputs = - old.nativeBuildInputs - ++ final.resolveBuildSystem { - editables = [ ]; - }; - }); - } - // lib.optionalAttrs hasFunctional { - pnt-functional = prev.pnt-functional.overrideAttrs (old: { + lib.genAttrs purePackageNames ( + name: + prev.${name}.overrideAttrs (old: { nativeBuildInputs = old.nativeBuildInputs ++ final.resolveBuildSystem { editables = [ ]; }; - }); - } + }) + ) ) ] ) @@ -144,10 +132,12 @@ editablePythonSets = lib.mapAttrs (_: mkEditablePythonSet) pythonVersions; # Rust checks from per-package modules (using default Python version) - rustChecks = (mkPackageModule pythonVersions.py313).checks; + rustChecks = lib.foldl' ( + acc: name: acc // (mkPackageModule name pythonVersions.py313).checks + ) { } maturinPackageNames; in { - checks = lib.optionalAttrs hasCli rustChecks; + checks = lib.optionalAttrs hasMaturinPackages rustChecks; _module.args = { inherit @@ -155,6 +145,10 @@ pythonSets editablePythonSets pythonVersions + packageNames + maturinPackageNames + purePackageNames + hasMaturinPackages ; defaultPython = pythonVersions.py313; };