# G-033 `proj:` package-mode scope prefix: visitor binary resolves dev/src against the cwd's project Status: achieved 2026-07-28 Scope: src/vfs/_config/punk_main.tcl (package-mode dispatch and boot-time root discovery); src/modules/punk/repo-999999.0a1.0.tm (find_project / is_project_root - reuse or lean boot mirror); bin/AGENTS.md (mode docs) Goal: a `proj:` prefix on the package-mode string (e.g. `punkshell proj:internal-src shell`) makes the dev/src path blocks resolve against the punk project containing the current working directory - walking up to the nearest VCS repo root, the marker punk::repo::is_project_root already uses - instead of the executable's own project, so an installed ("visitor") punkshell binary, including one downloaded standalone with no source tree around it, can interactively explore a project that builds no shell-capable binary or no binary at all; the prefix scopes WHICH project, staying outside the ordered dash-list whose block order remains the same-version tie-break dial, and the explicit prefix gates the behaviour so no `cd` silently rebinds a normal launch. Acceptance: ` proj:internal-src shell` (the documented canonical visitor invocation - kit copies win same-version ties, protecting the visiting shell's infrastructure; `proj:src` is the documented faithful-vintage variant where the project's copies win) launched from within a punk project's tree discovers the project root by walking up from cwd to the nearest VCS repo root and feeds it to the existing src-mode path machinery (src/modules, src/bootsupport/modules, src/vendormodules on the module path, src/lib on auto_path), so the session loads the project's dev-versioned modules while ordinary version resolution still lets the project supply anything the kit lacks or exceeds; the launch reports the detected project root and effective path precedence (never a silent rebind); with no project found walking up from cwd, or a `proj:` string containing no root-using block (dev/src), it warns and proceeds without false rebind; proven for a standalone binary outside any source tree (internal = kit contents regardless of binary location) against a project that builds no executable (tomlish is the natural first); exe-relative `src`/`dev` discovery is unchanged for a binary that IS in a project's bin/; the packagemode help text drafted in the detail file becomes the live punk::args documentation when implemented (rendered via G-032). ## Context The earlier intent (recalled 2026-07-07): an installed punkshell binary sitting in PATH - possibly a downloaded binary, not built from any local source tree - should be able to detect when the cwd is inside a punkshell project and set up the appropriate source/dev library resolution for it, so the user can explore that project interactively via the `shell` subcommand. The project being worked on might not build a shell-capable binary, or might build no executable at all (e.g. a modules-only project). History check: this was **never active** in the full sense. `src` mode is new (checkin 3d2d098d) and discovers the project root **exe-relative** (backtrack from the executable's `bin/`); `dev` is likewise exe-relative; `os` is the only cwd-relative piece and only shallowly (adds `[pwd]` and cwd-relative `modules`/`modules_tcl` ad-hoc `.tm` sources - no walk-up, no `src/` structure). The comments in punk_main.tcl name "the info nameofexecutable or info script or pwd relative paths" as design axes, so the intent seeded `os` mode, but cwd project-structure detection was never built into the boot modes. The cwd-relative detector **already exists at the runtime layer**: `punk::repo::find_project` walks up from cwd via `is_project_root`, and `dev` commands (module.templates, module.new, ...) already use it. The marker question is answered: `is_project_root` defines a project as a **VCS repo root** (git or fossil) that is not an excluded location - deliberately NOT relying on `punkproject.toml` (its own comment notes that file also appears in layout templates; tomlish has no punkproject.toml yet but IS a fossil root). This goal aligns the boot-time package modes with that existing runtime detection. ## Syntax: scope prefix, not a fifth token (decided 2026-07-07) The existing modes (internal/dev/src/os) are **ordered path-source blocks**: punk_main assembles `new_tm_path` token-by-token in dash-string order, and because the head of tcl::tm::list wins same-version ties (rules verified 2026-07-06), **earlier blocks win same-version collisions** - deliberately so (punk_main comment: "the lib/module paths based on the project being run should take preference if 'dev' is earlier in the list, even if the version number is the same"). The dash-list is the user's tie-break dial. `proj` answers a different question - WHICH project root dev/src resolve against - so it has no meaningful position in that ordering; a positional token (`os-proj-src-internal`) reads as noise because it is a category error. Decided syntax: [proj:][-...] e.g. proj:internal-src - The `proj:` prefix scopes root discovery to cwd-walk-up; everything after the colon is the unchanged ordered block string with unchanged semantics. - A `proj:` string containing no root-using block (dev/src) warns that proj has no effect (os/internal do not reference a project root). - Parser impact is small: recognise an optional `proj:` prefix on arg 0, strip it, then validate dash-tokens as today. Explicit prefix = opt-in; no `cd` silently rebinds a normal launch. - Rejected: automatic cwd-detection without a token (silent rebind surprise); an explicit -flag route (less convenient for everyday use - user decision); a token-attached qualifier like `src@proj` (binds the root choice to a precedence slot; the root choice is launch-wide). ## Same-version shadowing: why kit-first is the canonical default (decided 2026-07-07) Order only decides **exact-version ties**. The project's own code is unaffected by the choice: dev-versioned modules (999999.0a1.0, package prefer latest) and higher-versioned modules load from the project in either order, and anything absent from the kit has no tie so the project supplies it. The decision governs only same-version-different-content drift - rare within one lineage (punkshell version-bump discipline, G-026 provenance), but **structurally expected in visitor mode**, where the binary and the visited project's bootsupport/ vendormodules snapshots are different vintages by definition. Failure modes are asymmetric: - Project-first (`proj:src`): faithful to the project's snapshot vintage (reproduces its bugs), but the visiting shell's own machinery loads the project's older copies - a stale project can degrade the visiting shell (the 2026-07-06 stale-textblock/shellfilter silent-stdout class, imported). - Kit-first (`proj:internal-src`): the binary's tested infrastructure survives ties; robust session; drifted same-version project copies are silently masked (less faithful, invisible when it matters). **Canonical documented invocation: `proj:internal-src` (kit-first).** The project's genuinely-own code wins in either order; ties involve infrastructure snapshots nobody explores; and for a standalone downloaded binary the kit is its only trustworthy lineage. `proj:src` is documented as the faithful-vintage variant for reproducing a project's behaviour exactly. No special-casing: both fall out of the existing ordering semantics (explicit `internal` placement), preserving one uniform mental model. Standalone-binary semantics (downloaded, installed on PATH, no source tree): - `internal` = the kit's own vfs contents - location-independent; kit-first needs nothing from any source tree. - plain `src`/`dev` (exe-relative) find no project and degrade to warned no-ops (existing behaviour) - `proj:` is precisely what gives such a binary a project context. - Trimmed kits (G-019) compose gracefully: kit-first only arbitrates ties, so modules missing from a trimmed kit still flow from the visited project. - Note for docs: installing a binary INTO some project's bin/ makes exe-relative modes adopt that project (by design - adoption by location). ## Required boot notice A `proj:` launch must report the detected project root and the effective path precedence (and warn-and-proceed when no project is found walking up from cwd) - the rebind must never be silent. Candidate (non-acceptance) hardening: a shadow-report diagnostic listing name+version collisions between kit-internal and project paths, pairing naturally with G-025's buildinfo. ## packagemode help text (source draft for G-032) This is the designated source text for the packagemode leader in the launcher's punk::args definition (G-032 renders it; making it live is part of THIS goal's implementation - live help must not describe unimplemented behaviour, so it ships with the prefix, not before): packagemode -optional 1 -default internal -help "Package-source mode: an ordered dash-separated list of path blocks, optionally scoped with the 'proj:' prefix. Blocks (each adds a group of module/library paths): internal - paths inside the executable's kit (always included; appended last unless listed explicitly) dev - the project's built output (/modules ...) src - the project's unbuilt source (src/modules, src/bootsupport/modules, src/vendormodules, src/lib) os - ad-hoc .tm paths from the current directory/environment Without 'proj:', dev/src resolve against the executable's own project (executable in /bin). With 'proj:', they resolve against the nearest project root at or above the current directory (a git or fossil repository root) - for an installed punkshell binary visiting a project that builds no shell of its own. ORDER MATTERS: earlier blocks win when the same version of a module exists in more than one location. Examples: src work on the executable's own project source proj:internal-src explore the cwd project; the executable's own copies win version ties (robust visitor default) proj:src explore the cwd project; ITS copies win version ties (faithful to the project's snapshot vintage) The launch reports the detected project root and effective precedence." ## Approach - On `proj:`: walk up from `[pwd]` to the nearest VCS repo root (the `is_project_root` marker) and feed that root into the **existing src-mode path machinery** (already parameterised on a root variable - adds src/modules, src/modules_tcl, src/bootsupport/modules, src/vendormodules to the tm path, src/lib to auto_path, registers #modpod modules, sets package prefer latest). Only the discovery is new; the setup is reused. - Boot chicken-and-egg: find_project/is_project_root live in punk::repo, not loadable at the early package-mode phase. Options, decision to record: (a) lean inline VCS-root walk-up in punk_main using only Tcl builtins (fossil: _FOSSIL_/.fslckout/.fos; git: .git) - matches how the src-mode #modpod scan already inlines logic at boot; (b) defer proj setup to a post-boot phase where punk::repo is available. ## Notes - The related `dev` machinery (module.templates -> punk::mix::commandset::module -> punk::cap `punk.templates` plugin handlers) uses find_project and has unpolished plugin issues, plus an 8.6 shell-context zip-modpod-mount failure (G-034). This goal deliberately does NOT gate on synchronising with that machinery - it only aligns with the find_project detection concept. - Related: G-018 (the installable generic/plain binary this serves), G-019 (trimmed kits borrow from the visited project gracefully under kit-first), G-031 (boot core is where proj discovery belongs; the template project_main.tcl lacks even src mode - fork drift), G-032 (renders the packagemode help text above), G-015 (`proj:... script` also benefits), G-027 (if a universal project marker is ever seeded, proj could prefer it over the structural VCS heuristic). - G-016 relationship (recorded 2026-07-24 after overlap review; neither file previously referenced the other): G-016's projects.work registry enumeration and this goal's cwd-anchored walk-up are the two project-discovery surfaces (machine registry vs VCS-root walk) - a project findable by one should be findable by the other; both ride punk::repo. - G-047 relationship: G-047's primary-VCS resolution anchors at the same is_project_root marker proj: uses; its nested-punkproject.toml rule and this goal's root walk must agree. - G-119 relationship (G-119 achieved 2026-07-25 - see goals/archive/G-119-runtime-active-freshness.md): its freshness-verdict contract now sits in bin/AGENTS.md's runtime-manager contract, the same doc this goal's package-mode docs will extend - the mode docs must not contradict it on naming (artifact-named actives, -rN parsing). - G-089 relationship (recorded 2026-07-24 after overlap review): G-089's proj: scriptlib resolution rides this goal's visitor root discovery - its Notes already flag that `proj:... script` benefits; the two share the same root walk and the same never-silent rebind rule. - Archived-goal references in this file: G-015 achieved 2026-07-07 (goals/archive/G-015-script-subcommand-piped-stdin.md). - Related: G-043 references this goal one-directionally (visitor binaries: shellproject-anchored resources vs cwd-project resources) - relationship recorded at activation (2026-07-28 goals_xref survey; the other moderate-score overlaps G-123/G-127/G-131 share only punk_main.tcl/bin co-location, no proj: semantics). ## Progress Implemented and verified 2026-07-28 (activation and achievement same day; punkshell 0.29.0, punk::args::moduledoc::punkexe 0.1.1): - Boot chicken-and-egg decision: option (a) - lean inline walk-up in punk_main.tcl using only Tcl builtins, as `::punkboot::proj_root_find` (remains defined post-boot for probing, like the G-129 mount procs). It mirrors punk::repo::is_project_root including the is_candidate_root requirements (unwise-path exclusion; ./src plus one of src/modules, src/vfs, src/scriptapps, or punkproject.toml) so boot-time and runtime discovery agree (the G-016 findable-by-both principle). One recorded divergence: `.git` is accepted as file OR directory (worktree/submodule checkouts), where punk::repo::is_git_root requires a directory. - proj: with an invalid mode remainder is not consumed (whole arg passes through as subcommand/script, matching the existing invalid-mode fallthrough); bare `proj:` is consumed as internal and then warned as having no root-using block. - dev under proj: resolves /modules{,_tcl} and /lib{,_tcl} only - the exe-relative and cwd-relative lib candidates of the exe-anchored dev mode are deliberately not mixed in. - Verification (punk91 kit rebaked with the new boot; standalone copy of the kit placed in a session scratchpad outside any source tree): - `punkvisit.exe proj:internal-src script ` from c:/repo/jn/tclmodules/tomlish (fossil root, builds no executable): root detected and reported, precedence line reported, tomlish src paths appended after kit-internal paths, and `package require tomlish` -> 999999.0a1.0 sourced from tomlish/src/modules. - Same from C:/repo/jn/shellspy: "src mode: registered 6 #modpod modules" (modpod scanner fed by the visited root) and the punkexe moduledoc loads from the visited project's src/modules at 999999.0a1.0. - `proj:src` variant: tomlish src paths BEFORE kit-internal paths (faithful-vintage ordering). - No-project case (cwd = scratchpad): warns and proceeds, script still runs, dev/src unbound. - `proj:os`: warns 'proj:' has no effect (no dev/src block). - Exe-relative regression: `bin/punk91 src script ` unchanged (modpods registered, dev module versions load); piped `punk` subcommand under proj:internal-src evaluates in the shell environment with the visited project's modules (its exit code 1 on piped `exit` is pre-existing, present without proj:). - packagemode help text is live: the drafted leader text (extended with the proj: wording) sits in punk::args::moduledoc::punkexe 0.1.1 as the packagemode leader of `(script)::punkexe` (@leaders max raised to 2); rendering/parse wiring remains G-032's lane. The freshly deployed kit still carries moduledoc 0.1.0 - the 0.1.1 stamp reaches kits at the next vfscommonupdate promotion (deliberately not run from a dirty tree in this change-set). - Docs: bin/AGENTS.md launch-package-modes section (proj: contract + canonical invocations), src/vfs/AGENTS.md punk_main contract line, ARCHITECTURE.md entry-point bullet; CHANGELOG 0.29.0 entry (minor bump - new launch invocation).