@gotgenes/pi-permission-system 33.0.4 → 33.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [33.0.5](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v33.0.4...pi-permission-system-v33.0.5) (2026-09-20)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **pi-permission-system:** stop projecting an interpreter's inline script as a path ([f919704](https://github.com/gotgenes/pi-packages/commit/f919704d029d6cae6027f50ce89cc49d76f35358)), closes [#863](https://github.com/gotgenes/pi-packages/issues/863)
14
+
15
+ ### Documentation
16
+
17
+ * **pi-permission-system:** record the interpreter script role in the roadmap and module tree ([630e29f](https://github.com/gotgenes/pi-packages/commit/630e29f2365fb1848a835078f5b28cb5462b0b94))
18
+
8
19
  ## [33.0.4](https://github.com/gotgenes/pi-packages/compare/pi-permission-system-v33.0.3...pi-permission-system-v33.0.4) (2026-09-20)
9
20
 
10
21
 
@@ -118,6 +118,7 @@ For allowlisted commands, all non-flag positional arguments are assumed to be pa
118
118
  **This extension** extracts path candidates from all commands generically, then applies additional intelligence:
119
119
 
120
120
  - A `PATTERN_FIRST_COMMANDS` map understands flag arity for `sed`, `awk`, `grep`, `rg`, and similar tools, distinguishing inline patterns/scripts from file arguments to avoid false positives.
121
+ The same table reads an interpreter's inline script (`node -e`, `bun --eval`, `python3 -c`, `perl -e`, `ruby -e`) as a script rather than an operand, while leaving a script *file* (`node build.js`) an operand.
121
122
  - Redirect destinations (`> /path/to/file`) are extracted.
122
123
  - Heredoc bodies, comments, and variable assignments are skipped.
123
124
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gotgenes/pi-permission-system",
3
- "version": "33.0.4",
3
+ "version": "33.0.5",
4
4
  "description": "Permission enforcement extension for the Pi coding agent.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -36,10 +36,11 @@ export interface PathToken {
36
36
  * {@link SKIP_SUBTREE_TYPES} check: `heredoc_body` is in both sets, and the
37
37
  * host reading is the one that must win.
38
38
  *
39
- * For commands in `PATTERN_FIRST_COMMANDS`, uses position-based
40
- * argument skipping to avoid collecting inline patterns/scripts
41
- * as path candidates. For all other commands, collects all
42
- * arguments generically.
39
+ * For commands in `PATTERN_FIRST_COMMANDS`, uses position- and role-based
40
+ * argument skipping to avoid collecting inline patterns/scripts as path
41
+ * candidates — a leading pattern positional for a matching tool, a
42
+ * `script`-role flag's argument for an interpreter. For all other commands,
43
+ * collects all arguments generically.
43
44
  */
44
45
  export function collectPathCandidateTokens(node: TSNode): PathToken[] {
45
46
  if (node.type === "command") return collectCommandTokens(node);
@@ -499,19 +500,124 @@ const SD_CONFIG: PatternCommandConfig = {
499
500
  };
500
501
 
501
502
  /**
502
- * Commands whose first N positional arguments are inline patterns/scripts,
503
- * not filesystem paths. The map stores per-command flag configuration so
504
- * the walker can correctly identify which arguments are consumed by flags
505
- * vs. which are positional.
503
+ * An interpreter takes its inline script from a flag and never from a leading
504
+ * positional, so `patternPositionals: 0` — `node build.js /tmp/x` names two
505
+ * real operands and no script.
506
+ *
507
+ * Verified by execution on macOS, 2026-09-20, node v26.9.0: `node -e`,
508
+ * `node --eval`, `node --eval='…'`, `node -p '1+1'` — `2`, and
509
+ * `node --print '2+2'` — `4` all run their argument as the program.
510
+ * `node -p t.js` evaluates `t.js` as *source* rather than running the file
511
+ * (`[eval]:1 / t.js / ^`), so `-p` consumes its argument unconditionally.
512
+ */
513
+ const NODE_CONFIG: PatternCommandConfig = {
514
+ flags: new Map<string, PatternFlagRole>([
515
+ ["-e", "script"],
516
+ ["--eval", "script"],
517
+ ["-p", "script"],
518
+ ["--print", "script"],
519
+ ]),
520
+ patternPositionals: 0,
521
+ };
522
+
523
+ /**
524
+ * `bun` asserts the same four spellings as `node` and gets its own object
525
+ * rather than sharing one, because the table's rule is a shared *parser* and
526
+ * not a shared spelling — the two are different binaries (#823).
527
+ *
528
+ * Verified by execution, bun 1.4.2: `bun -e`, `bun --eval`, `bun -p '1+1'`
529
+ * — `2`, `bun --print '3+3'` — `6`; `bun -p` with no value errors
530
+ * `The argument '-p' requires a value but none was supplied.`, so it consumes
531
+ * unconditionally.
532
+ */
533
+ const BUN_CONFIG: PatternCommandConfig = {
534
+ flags: new Map<string, PatternFlagRole>([
535
+ ["-e", "script"],
536
+ ["--eval", "script"],
537
+ ["-p", "script"],
538
+ ["--print", "script"],
539
+ ]),
540
+ patternPositionals: 0,
541
+ };
542
+
543
+ /**
544
+ * `python` and `python3` share one object because they are the same
545
+ * interpreter family: every implementation either name reaches is a
546
+ * CPython-compatible front end where `-c` takes the following argument.
547
+ *
548
+ * Verified by execution, python3 3.14.7: `python3 -c 'print("PC-OK")'`, and
549
+ * `python3 -cu 'print("x")'` raises from `File "<string>", line 1` — the
550
+ * glued `u` is evaluated as the script, which is the getopt semantics the
551
+ * existing glued-value rule already models. No `python` binary exists on the
552
+ * authoring host, so its row rests on the family argument rather than a run.
553
+ */
554
+ const PYTHON_CONFIG: PatternCommandConfig = {
555
+ flags: new Map<string, PatternFlagRole>([["-c", "script"]]),
556
+ patternPositionals: 0,
557
+ };
558
+
559
+ /**
560
+ * Verified by execution, perl 5.34.1: `perl -e 'print "PE-OK\n"'` and
561
+ * `perl -E 'say "PE2-OK"'`; `perl -e` with nothing after it errors
562
+ * `No code specified for -e.`, so both consume unconditionally.
563
+ *
564
+ * `-p` and `-n` are deliberately absent. They take no argument of their own,
565
+ * and the cluster spelling that carries the script (`perl -pe 's|a|b|'`) is
566
+ * looked up as `-p` by the glued rule's `text.slice(0, 2)` — so listing `-p`
567
+ * would consume the following word on the *separated* spelling too and drop a
568
+ * real operand, the direction ADR 0009 forbids.
569
+ */
570
+ const PERL_CONFIG: PatternCommandConfig = {
571
+ flags: new Map<string, PatternFlagRole>([
572
+ ["-e", "script"],
573
+ ["-E", "script"],
574
+ ]),
575
+ patternPositionals: 0,
576
+ };
577
+
578
+ /**
579
+ * Verified by execution, ruby 4.0.7: `ruby -e 'puts "RE-OK"'`.
580
+ *
581
+ * `-E` is deliberately **not** listed, though `perl` lists it: on `ruby` it is
582
+ * `--encoding`, not a script flag. `ruby -E utf-8 -e 'puts "RE2-OK"'` runs,
583
+ * proving `-E` consumed `utf-8` and left the script to `-e`. Leaving it
584
+ * unlisted over-surfaces `utf-8` as a token that names nothing, which the
585
+ * existence probe discards — the recoverable direction.
586
+ */
587
+ const RUBY_CONFIG: PatternCommandConfig = {
588
+ flags: new Map<string, PatternFlagRole>([["-e", "script"]]),
589
+ patternPositionals: 0,
590
+ };
591
+
592
+ /**
593
+ * Commands whose leading positional arguments are inline patterns/scripts
594
+ * rather than filesystem paths, and commands whose inline script arrives
595
+ * through a flag. The map stores per-command flag configuration so the walker
596
+ * can identify which arguments a flag consumes and which are positional.
597
+ *
598
+ * Two classes share the table because they share the question. A pattern-first
599
+ * *matching* tool (`sed`, `grep`, `rg`) leads with a pattern and skips one or
600
+ * two positionals; an **interpreter** (`node`, `bun`, `python`, `perl`,
601
+ * `ruby`) leads with nothing and skips none, so its script can only ever
602
+ * arrive through a `script`-role flag and a script *file* stays an operand
603
+ * (#863).
506
604
  *
507
605
  * Names share a configuration object only when they share a *parser*, which is
508
606
  * narrower than being aliases: `egrep`/`fgrep` are the same binary as `grep`
509
607
  * here, and `nawk` is one-true-awk like `awk` — but `gawk` has its own config,
510
608
  * because it is the only one of the three that certainly means GNU awk and so
511
609
  * the only one whose long options certainly consume (#823).
610
+ * `node` and `bun` split for the same reason from the other direction: they
611
+ * assert identical spellings and are different binaries.
512
612
  */
513
613
  const PATTERN_FIRST_COMMANDS: ReadonlyMap<string, PatternCommandConfig> =
514
614
  new Map([
615
+ ["node", NODE_CONFIG],
616
+ ["bun", BUN_CONFIG],
617
+ ["python", PYTHON_CONFIG],
618
+ ["python3", PYTHON_CONFIG],
619
+ ["perl", PERL_CONFIG],
620
+ ["ruby", RUBY_CONFIG],
515
621
  ["sed", SED_CONFIG],
516
622
  ["awk", AWK_CONFIG],
517
623
  ["gawk", GAWK_CONFIG],