Browse Source
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.commaster
3 changed files with 160 additions and 0 deletions
@ -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 |
||||
`<punkexe> <somefile>` 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 |
||||
|
||||
- `<punkexe> script <file> [<args>...]` - execute the file with conventional |
||||
::argv0/::argv semantics, exit with its status. |
||||
- `... | <punkexe> 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. |
||||
@ -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 <glob>` (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. |
||||
Loading…
Reference in new issue