From 5c5174f73034064d8135cfd307db2a8afdea9c25 Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Mon, 6 Jul 2026 15:29:20 +1000 Subject: [PATCH] add goals G-015/G-016/G-017: piped script calls + projects.work discovery G-015: complete the punk executable `script` subcommand - run a script file or piped stdin (scriptname optional) in an interp preloaded with the default punk shell modules/aliases, honest exit codes, no shellfilter transforms, no interactive-shell fallthrough. Replaces the fragile `...;exit | punkexe` repl-pipe pattern (currently a stub in punk_main.tcl that only emits stderr diagnostics). Lean bare-script execution deferred to a later `tclsh` subcommand expansion. G-016: extend `dev projects.work` to discover git-based projects alongside fossil (git has no central registry - enumeration-source candidates recorded in the detail file). G-017 (index-only): once G-015 lands, direct agents via repo guidance to locate sibling projects with a piped projects.work call instead of scanning the filesystem. Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com --- GOALS.md | 17 ++++ goals/G-015-script-subcommand-piped-stdin.md | 85 ++++++++++++++++++++ goals/G-016-projects-work-git-discovery.md | 58 +++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 goals/G-015-script-subcommand-piped-stdin.md create mode 100644 goals/G-016-projects-work-git-discovery.md diff --git a/GOALS.md b/GOALS.md index a9ae9ea1..af445e3c 100644 --- a/GOALS.md +++ b/GOALS.md @@ -133,3 +133,20 @@ Scope: src/modules/punk/config-0.1.tm, src/modules/punk/repl-999999.0a1.0.tm, sr Detail: goals/G-014-punk-config-toml.md Goal: ::punk::config loads stored configuration from toml files in the XDG-located config dir - parsed via the vendored tomlish module, never an ad-hoc parser - and consumers resolve settings with per-named-subshell overrides, so features like the G-013 debug-view startup defaults read declared user configuration instead of hardcoded fallbacks. Acceptance: a setting declared in a toml file under the XDG-located config dir is visible through the punk::config API at repl startup, and with no config files present built-in defaults apply with no errors beyond the existing missing-dir notice; a named subshell resolves its own overriding value for a key also defined at the parent/default scope, and a subshell with no override inherits the outer value (proven with at least one real key); at least one shipped feature (the G-013 editbuf-view startup default is the natural first) reads its default through this path rather than a hardcoded value; all toml reading/writing in punk::config goes through the tomlish module, and the tomlish API procs punk::config consumes carry punk::args (PUNKARGS) documentation - added upstream in the tomlish project and re-vendored here before punk::config implementation proceeds. + +### G-015 [proposed] Punk executable `script` subcommand: reliable non-interactive piped/script execution +Scope: src/vfs/_config/punk_main.tcl, src/lib/app-punkshell/punkshell.tcl (script path or a leaner dedicated app package) +Detail: goals/G-015-script-subcommand-piped-stdin.md +Goal: ` script [] [...]` executes a script file or piped stdin content (scriptname optional when input is piped) in an interp preloaded with the basic punk modules and aliases a punk shell provides by default, and always terminates with the script's success/failure as its exit code - without the `shell` subcommand's shellfilter channel transforms/logging stacks and without ever dropping into an interactive shell - so agents can reliably make piped script calls to punk executables. +Acceptance: piping commands to ` script` runs them and terminates at stdin EOF with no trailing `exit` required, exit code 0 on success; a failing piped command terminates the process with a nonzero exit code and the error on stderr, never landing in an interactive shell regardless of console availability or PUNK_PIPE_EOF; ` script [...]` executes the file with conventional ::argv0/::argv and propagates its error status the same way; the script path installs none of the `shell` subcommand's shellfilter stacks/transforms and the launch plumbing itself emits nothing on stdout/stderr (the current stub's stderr diagnostics removed) so exec-style callers see only the script's own output; the motivating example works with no package require boilerplate: piping `dev projects.work **` to ` script` emits the matching-project table and exits 0, because the script interp carries the default punk shell module/alias environment. + +### G-016 [proposed] `projects.work` discovers git-based projects alongside fossil +Scope: src/modules/punk/mix/commandset/project-999999.0a1.0.tm, src/modules/punk/repo-999999.0a1.0.tm +Detail: goals/G-016-projects-work-git-discovery.md +Goal: `dev projects.work ` lists git-based project checkouts as well as fossil-based ones, each result identifying its VCS - fossil discovery stays central-config-db based, and git discovery uses a defined enumeration source (git has no central registry; the chosen mechanism is recorded in the detail file). +Acceptance: with a git-only project on disk registered in the chosen enumeration source and matching the glob, `projects.work` lists its working directory and identifies it as git; existing fossil results are unchanged apart from any added VCS-identifying column; a project that is both git and fossil (e.g. this repo) appears with both indicated rather than duplicated; glob matching remains case-insensitive. + +### G-017 [proposed] Agents locate local projects via piped `projects.work` calls, not filesystem scanning +Scope: AGENTS.md (root) or a child doc it indexes (guidance content only - no code) +Goal: once G-015 makes piped script calls reliable, repository guidance directs agents asked to locate another local project to query it via a piped `projects.work` call to a punk executable instead of grepping/globbing the wider filesystem. +Acceptance: root AGENTS.md (or a child doc indexed from it) records the exact recommended invocation - executable, subcommand, glob usage, expected output shape - and states when filesystem scanning remains appropriate (projects not registered in any discovery source); the guidance is added only after G-015 is achieved (and notes the fossil-only limitation until G-016); following the documented pattern, an agent locates a named sibling project's checkout dir with a single piped call. diff --git a/goals/G-015-script-subcommand-piped-stdin.md b/goals/G-015-script-subcommand-piped-stdin.md new file mode 100644 index 00000000..162ab9e6 --- /dev/null +++ b/goals/G-015-script-subcommand-piped-stdin.md @@ -0,0 +1,85 @@ +# G-015 Punk executable `script` subcommand: reliable non-interactive piped/script execution + +Status: proposed +Scope: src/vfs/_config/punk_main.tcl, src/lib/app-punkshell/punkshell.tcl (script path or a leaner dedicated app package) +Acceptance: as in root GOALS.md index (canonical). + +## Context + +Agents (and exec-style callers generally) currently have no reliable way to run a +one-shot command against a punk executable. The working pattern is to pipe repl +input into the default shell path, e.g. (powershell): + + 'dev projects.work *tomlish*;exit' | punk902z + +This is fragile in two ways: + +1. The trailing `exit` is load-bearing: without it, stdin EOF triggers the + app-punkshell eof handling, whose default heuristic policy attempts to reopen + the console (`CONIN$` / `/dev/tty`) and drop into an interactive repl. When a + console exists but no human is watching (typical agent/CI harness), that is a + permanent hang. `PUNK_PIPE_EOF=exit` mitigates, but callers must know to set it. +2. If any command before the `exit` errors, the repl keeps consuming input and + the `exit` line may never run as intended - the session can still land + interactive, and the process exit code does not reflect the failure. + +Meanwhile the `script` subcommand in the punk_main.tcl dispatch +(src/vfs/_config/punk_main.tcl ~1163) is an incomplete stub: it prints stderr +diagnostics ("main.tcl launching script with args: ...") and sets +::tcl_interactive, but never actually executes anything. Note also the dispatch +default (~1090): any first argument that is not a recognised mode/subcommand is +reclassified as `script`, so completing this path also completes the bare +` ` form. + +The `shell` subcommand path (app-punkshell) wraps the session in shellfilter +channel stacks (`punkshellout`/`punkshellerr` with file/syslog logging targets, +plus ansiwrap on stderr in places). Those transforms exist for interactive-shell +ergonomics and logging; for a one-shot script call they add overhead, alter +output, and risk stderr chatter - which matters because Tcl `exec` treats any +stderr output as command failure by default (see the comment block in +punk_main.tcl's `punk` subcommand case). + +## Approach + +- ` script [...]` - execute the file with conventional + ::argv0/::argv semantics, exit with its status. +- `... | script` - read the whole of stdin as the script when no + scriptname is supplied and stdin is piped/redirected; terminate at EOF, never + reopening a console. The scriptname form must still work with piped stdin + present (the script may itself want to read stdin) - "with or without a + trailing scriptname". +- No shellfilter stacks/transforms on the script path; plain channels. +- No launcher output of its own on stdout or stderr (remove the stub + diagnostics); the script's own output is the only output. +- Errors: report on stderr, exit nonzero. Success: exit 0 (or the script's own + explicit exit code). No interactive fallback on this subcommand under any + eof/error condition - PUNK_PIPE_EOF and console availability are irrelevant + here by design. +- The script interp loads the basic punk modules and aliases that are available + by default in an interactive punk shell, so commands like `dev projects.work` + work in a piped one-liner with no `package require` boilerplate. This is + deliberately not the leanest environment for basic scripts - fast/bare script + execution is the future province of an expanded `tclsh` subcommand, out of + scope here. The exact recommended one-liner gets documented under G-017. + +## Alternatives considered + +- Hardening the shell/repl pipe path instead (PUNK_PIPE_EOF, error-aborts-input) + - insufficient alone: the shell path's channel transforms and repl semantics + are wrong for exec-style callers even when eof/error policy is fixed; a + dedicated clean path is simpler than making the interactive path dual-purpose. +- Requiring agents to set PUNK_PIPE_EOF=exit and keep the trailing `exit` - + rejected as the durable answer: it leaves exit codes meaningless on mid-script + errors and depends on every caller knowing the incantation. +- A bare interp (no punk modules/aliases preloaded) for the script path - + rejected for this subcommand: the point is that shell-default commands + (`dev projects.work` etc.) work as piped one-liners; lean bare-script + execution belongs to a later expansion of the `tclsh` subcommand. + +## Notes + +- The eof policy machinery (PUNK_PIPE_EOF exit/interactive/heuristic) in + app-punkshell (~punkshell.tcl 466-507) stays as-is for the `shell` subcommand; + this goal does not change interactive-shell behaviour. +- Related later goal: G-016 extends what the motivating `projects.work` call can + find; G-017 documents the agent-facing invocation pattern once this lands. diff --git a/goals/G-016-projects-work-git-discovery.md b/goals/G-016-projects-work-git-discovery.md new file mode 100644 index 00000000..4bab8b0e --- /dev/null +++ b/goals/G-016-projects-work-git-discovery.md @@ -0,0 +1,58 @@ +# G-016 `projects.work` discovers git-based projects alongside fossil + +Status: proposed +Scope: src/modules/punk/mix/commandset/project-999999.0a1.0.tm, src/modules/punk/repo-999999.0a1.0.tm +Acceptance: as in root GOALS.md index (canonical). + +## Context + +`dev projects.work ` (punk::mix::commandset::project::collection::work) +answers "where are my projects checked out" by opening the central fossil +config-db, globbing repository database filenames, and listing the known +checkout directories per repo - with optional per-checkout file-state detail. +This makes it the natural mechanism for agents to locate sibling projects +(e.g. the tomlish project space referenced by G-014) instead of recursively +scanning the filesystem. + +The gap: it is fossil-only. Git-based projects are invisible to it, and git has +no equivalent of fossil's central config-db - there is no built-in registry of +clones on a machine. So git discovery needs a defined enumeration source of its +own before the listing can be extended. + +## Approach + +Enumeration-source candidates (implementation decision, to be recorded here when +made): + +- **Configured search roots**: a punk::config setting (natural G-014 consumer) + listing parent directories to scan one or two levels deep for `.git` dirs. + Bounded scan, no registry maintenance, but discovery limited to declared roots. +- **Punk-maintained registry**: record project paths when punk tooling + creates/opens them (and offer a scan-once command to seed it). Fast lookups, + works for arbitrary locations, but can go stale. +- Hybrid: registry seeded/refreshed by an explicit scan of configured roots. + +Result-shape considerations: + +- Each row identifies its VCS. Fossil rows keep their current columns + (repo db filename, project name/code, checkout dirs, dup-set annotations); + what the git analogue of "project name" is (dir name, remote URL tail, + configured name) is part of the design work. +- A dual git+fossil workdir (like the shellspy repo itself) is one project row + with both VCSs indicated, not two rows. +- Fossil supports multiple checkouts per repo db; git worktrees are the + analogous multi-workdir case and should at least not break the listing. + +## Alternatives considered + +- Filesystem-wide scanning at query time - rejected: unbounded cost and exactly + the behaviour this mechanism is meant to replace for agents. +- Relying on external tools' state (e.g. IDE/zoxide/gh caches) - rejected: + non-portable, not present on all machines, opaque formats. + +## Notes + +- Depends on nothing, but its value to agents is realised through G-015 + (reliable piped invocation) and G-017 (agent guidance documenting the call). +- The `-cd` / `-detail` options and case-insensitive glob behaviour of the + existing command are contracts to preserve.