You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

7.1 KiB

G-012 Template system: inert VCS-config payloads and explicit layout refresh

Status: proposed Scope: src/project_layouts/, src/make.tcl, src/modules/punk/mix/ (layout instantiation), fauxlink module (bootsupport 0.1.1 - promoted if chosen as mechanism) Goal: project layouts carry no live nested VCS-config files - template .gitignore payloads are stored inert (renamed, or fauxlink-encoded) and materialized at project generation - and src/make.tcl has an explicit punkcheck-tracked step that refreshes layout payloads from their canonical sources. Acceptance: a scan of src/project_layouts finds no file named .gitignore, and git check-ignore --no-index over every layout file matches only root-.gitignore rules (nested rules provably inert); a project generated from each affected layout receives a working .gitignore whose content matches the canonical payload/target; after editing a canonical source (e.g. root .gitignore), the make.tcl template-refresh step updates the derived layout payloads (punkcheck-tracked), covering vendor/punk layouts as well as custom/_project; the previously hidden template files (layout READMEs, vendored TODO-class files) remain git-tracked without per-file force-add exceptions.

Context

Project layouts under src/project_layouts/ carry .gitignore files as payload - the ignore file a generated project is supposed to receive (essentially a copy of shellspy's own root .gitignore). But git has no concept of payload: any .gitignore anywhere in the working tree is live configuration for its subtree, with leading-/ patterns anchored to the nested file's own directory. The result is a template that self-censors - the layout ignores, in the shellspy repo, exactly what a generated project would ignore in itself.

Discovered 2026-07-06 during git/fossil tracked-set verification (fossil reads only the checkout-root .fossil-settings/ignore-glob, so it managed template files git silently dropped): four vendor/punk/* layouts each carry a live .gitignore, and .../project-0.1/{bin,lib,modules}/README.md plus bin/runtime/README.md were untracked in git. The bin/runtime case shows the nastier variant - git does not descend into an excluded directory, so negation patterns cannot resurrect deeper paths. The immediate stopgap was per-file git add -f (commit fc1c474c), which protects only existing files: any new file placed in a template's ignored spots drops silently again. The dual-VCS contract (.fossil-settings/AGENTS.md) records the git/fossil asymmetry and points here for the fix.

A secondary weakness: template payloads that mirror living files (the root .gitignore, build.tcl/make.tcl, bootsupport modules) have only partial refresh machinery. make.tcl's sync_layouts step (punkcheck-tracked) syncs just build.tcl and make.tcl, and only into the custom/_project layout base - the vendor/punk/* layouts have no refresh mechanism, so payload drift is unmanaged.

Approach

  1. Store VCS-config payloads inert. Two candidate mechanisms; the choice is an implementation decision to be recorded here when made:
    • Neutral rename: payload stored as e.g. _gitignore or gitignore.in; layout instantiation renames it to .gitignore in the generated project. Simple, no new dependency; templates keep a literal copy (drift handled by the refresh step).
    • Fauxlink: payload stored as <nominal>#<encoded-target>.fauxlink (see fauxlink module, currently bootsupport 0.1.1) and resolved at generation. Inert for git by construction, and the target can be the canonical root .gitignore itself - eliminating copy drift rather than managing it. Requires generation-time resolution in punk::mix and possibly promoting fauxlink to a first-class module dependency.
  2. Materialize at generation. The punk::mix layout-instantiation path produces a real .gitignore in the generated project regardless of mechanism, with content equal to the canonical payload/target.
  3. Explicit template refresh in make.tcl. Generalise the existing sync_layouts step into a defined template-refresh covering the vendor/punk/* layout base as well as custom/_project, and derived payloads beyond build.tcl/make.tcl (the gitignore payload, and prospectively other mirrored content). punkcheck-tracked like the existing step, so refreshes are recorded and skippable when sources are unchanged.
  4. Retire the stopgap. With inert payloads, the per-file force-adds from fc1c474c become ordinary tracked files (no ignore rule matches them); the accidental-ignore class is structurally impossible rather than patched per file.

Alternatives considered

  • Negation entries inside the nested template .gitignore files (e.g. !README.md) - rejected: pollutes the payload that generated projects receive, and cannot fix the excluded-directory case (bin/runtime/) because git does not descend into excluded dirs to evaluate negations.
  • Keep per-file git add -f only - rejected as the durable answer: silent drop recurs for every new file placed under a template's ignored paths; nothing structural changes.
  • .git/info/exclude or repo-local config tricks - rejected: not versioned/portable to other clones and contributors.
  • Do nothing on the git side (fossil sees the files) - rejected: git is the day-to-day VCS; a mirror having the truer view is a symptom, not a solution.

Notes

  • Affected today: src/project_layouts/vendor/punk/{basic,minimal,project-0.1,sample-0.1}/.gitignore. The same inert-payload treatment should apply to any future live-config payloads in layouts (e.g. .gitattributes); .fossil-settings/ payloads in layouts are inert by nature (fossil reads them only at a checkout root) and need no change.
  • AGENTS.md payloads are the same hazard class (identified 2026-07-07 while designing template DOX for derived projects, see G-027 detail): an AGENTS.md stored as template payload under src/project_layouts/ is live DOX for any agent whose work touches layout paths in the punkshell repo itself - the DOX walking rule makes it binding contract, though its instructions are written for a generated project (e.g. its build/test commands would operate on template internals). When layouts gain AGENTS.md payloads (per the G-027 documentation-ownership design), they need the same inert storage + materialize-at-generation treatment as the .gitignore payloads.
  • fauxlink background: target encoded in the filename (+ for /, url-style escapes), no filesystem support required, application-driven resolution - see src/bootsupport/modules/fauxlink-0.1.1.tm doctools header.
  • Verification tie-in: the dual-VCS sync checks in .fossil-settings/AGENTS.md double as a regression detector for this class - after this goal, the "git-tracked invisible to fossil" comparison should show no template-payload entries.
  • G-062 (archived) recorded a follow-on in this territory: seeding LICENSE.txt into generated project layouts (deliberately excluded from G-062's acceptance because src/project_layouts sync is restricted) - see goals/archive/G-062-project-license-file.md.