Browse Source

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
master
Julian Noble 1 week ago
parent
commit
5c5174f730
  1. 17
      GOALS.md
  2. 85
      goals/G-015-script-subcommand-piped-stdin.md
  3. 58
      goals/G-016-projects-work-git-discovery.md

17
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: `<punkexe> script [<scriptname>] [<args>...]` 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 `<punkexe> 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; `<punkexe> script <file> [<args>...]` 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 *<name>*` to `<punkexe> 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 <glob>` 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.

85
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
`<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.

58
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 <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…
Cancel
Save