@ordewell/core 0.4.8 → 0.4.9

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/dist/index.mjs CHANGED
@@ -118,13 +118,1233 @@ var AUTO_COMMANDS = [
118
118
  "jq",
119
119
  "yq"
120
120
  ];
121
+ var UNIVERSAL_FLAGS = ["--help", "--version"];
122
+ var COUNT_FLAG = /^-\d+$/;
123
+ var FLAG_POLICY = {
124
+ ls: {
125
+ cluster: true,
126
+ booleans: [
127
+ "-1",
128
+ "-a",
129
+ "-A",
130
+ "-b",
131
+ "-c",
132
+ "-C",
133
+ "-d",
134
+ "-D",
135
+ "-f",
136
+ "-F",
137
+ "-g",
138
+ "-G",
139
+ "-h",
140
+ "-H",
141
+ "-i",
142
+ "-k",
143
+ "-l",
144
+ "-L",
145
+ "-m",
146
+ "-n",
147
+ "-N",
148
+ "-o",
149
+ "-p",
150
+ "-q",
151
+ "-Q",
152
+ "-r",
153
+ "-R",
154
+ "-s",
155
+ "-S",
156
+ "-t",
157
+ "-u",
158
+ "-U",
159
+ "-v",
160
+ "-x",
161
+ "-X",
162
+ "-Z",
163
+ "--all",
164
+ "--almost-all",
165
+ "--author",
166
+ "--escape",
167
+ "--classify",
168
+ "--file-type",
169
+ "--full-time",
170
+ "--no-group",
171
+ "--human-readable",
172
+ "--si",
173
+ "--dereference-command-line",
174
+ "--inode",
175
+ "--kibibytes",
176
+ "--dereference",
177
+ "--numeric-uid-gid",
178
+ "--literal",
179
+ "--hide-control-chars",
180
+ "--show-control-chars",
181
+ "--quote-name",
182
+ "--reverse",
183
+ "--recursive",
184
+ "--size",
185
+ "--directory",
186
+ "--group-directories-first",
187
+ "--context",
188
+ "--zero"
189
+ ],
190
+ values: [
191
+ "-w",
192
+ "-I",
193
+ "-T",
194
+ "--width",
195
+ "--ignore",
196
+ "--hide",
197
+ "--block-size",
198
+ "--color",
199
+ "--format",
200
+ "--sort",
201
+ "--time",
202
+ "--time-style",
203
+ "--indicator-style",
204
+ "--quoting-style",
205
+ "--tabsize"
206
+ ]
207
+ },
208
+ tree: {
209
+ cluster: true,
210
+ // `-o` is absent on purpose: it is tree's write-to-a-file flag, which the
211
+ // redirect check never sees because there is no redirect.
212
+ booleans: [
213
+ "-a",
214
+ "-d",
215
+ "-f",
216
+ "-i",
217
+ "-l",
218
+ "-x",
219
+ "-C",
220
+ "-n",
221
+ "-p",
222
+ "-s",
223
+ "-h",
224
+ "-u",
225
+ "-g",
226
+ "-D",
227
+ "-t",
228
+ "-c",
229
+ "-r",
230
+ "-F",
231
+ "-q",
232
+ "-N",
233
+ "-Q",
234
+ "-v",
235
+ "-J",
236
+ "-X",
237
+ "-Z",
238
+ "--noreport",
239
+ "--dirsfirst",
240
+ "--du",
241
+ "--prune",
242
+ "--matchdirs",
243
+ "--inodes",
244
+ "--device",
245
+ "--si",
246
+ "--gitignore",
247
+ "--ignore-case",
248
+ "--filesfirst",
249
+ "--nolinks",
250
+ "--hintro"
251
+ ],
252
+ values: [
253
+ "-L",
254
+ "-P",
255
+ "-I",
256
+ "-H",
257
+ "--filelimit",
258
+ "--timefmt",
259
+ "--charset",
260
+ "--sort",
261
+ "--gitfile",
262
+ "--info",
263
+ "--prefix"
264
+ ]
265
+ },
266
+ git: {
267
+ cluster: true,
268
+ // Absent on purpose, and each for a reason that was demonstrated rather than
269
+ // guessed at:
270
+ // -c, --config-env set configuration inline, and several configuration
271
+ // keys name a program to run (`core.pager`, `core.editor`,
272
+ // `diff.external`), so this is an execution flag with one
273
+ // indirection in front of it.
274
+ // --exec-path redirects where git finds its helper programs; a
275
+ // fabricated helper ran during an ordinary remote listing.
276
+ // -O, --open-files-in-pager runs an arbitrary named program over search hits.
277
+ // --upload-pack, --receive-pack name the program run on the other end.
278
+ // --textconv, --ext-diff run filters the repository itself configures.
279
+ // --git-dir, --work-tree repoint git at a tree whose configuration is not
280
+ // the one the developer is looking at.
281
+ // -o, --output write a file. `git ls-files -o` is a legitimate spelling
282
+ // of `--others`, but `git archive -o` writes, and the
283
+ // long spelling stays available for the read.
284
+ booleans: [
285
+ "-a",
286
+ "-b",
287
+ "-E",
288
+ "-F",
289
+ "-h",
290
+ "-H",
291
+ "-i",
292
+ "-I",
293
+ "-l",
294
+ "-p",
295
+ "-P",
296
+ "-q",
297
+ "-r",
298
+ "-R",
299
+ "-s",
300
+ "-t",
301
+ "-u",
302
+ "-v",
303
+ "-w",
304
+ "-z",
305
+ "--oneline",
306
+ "--graph",
307
+ "--stat",
308
+ "--shortstat",
309
+ "--numstat",
310
+ "--dirstat",
311
+ "--summary",
312
+ "--name-only",
313
+ "--name-status",
314
+ "--raw",
315
+ "--patch",
316
+ "--no-patch",
317
+ "--patch-with-stat",
318
+ "--cached",
319
+ "--staged",
320
+ "--porcelain",
321
+ "--short",
322
+ "--long",
323
+ "--branch",
324
+ "--show-stash",
325
+ "--show-current",
326
+ "--all",
327
+ "--tags",
328
+ "--heads",
329
+ "--remotes",
330
+ "--list",
331
+ "--verbose",
332
+ "--quiet",
333
+ "--abbrev-ref",
334
+ "--count",
335
+ "--reverse",
336
+ "--first-parent",
337
+ "--no-merges",
338
+ "--merges",
339
+ "--follow",
340
+ "--full-history",
341
+ "--simplify-merges",
342
+ "--sparse",
343
+ "--dense",
344
+ "--ancestry-path",
345
+ "--left-right",
346
+ "--cherry",
347
+ "--cherry-pick",
348
+ "--cherry-mark",
349
+ "--boundary",
350
+ "--topo-order",
351
+ "--date-order",
352
+ "--author-date-order",
353
+ "--relative-date",
354
+ "--parents",
355
+ "--children",
356
+ "--timestamp",
357
+ "--no-walk",
358
+ "--do-walk",
359
+ "--decorate",
360
+ "--no-decorate",
361
+ "--abbrev-commit",
362
+ "--no-abbrev-commit",
363
+ "--abbrev",
364
+ "--no-abbrev",
365
+ "--color",
366
+ "--no-color",
367
+ "--pretty",
368
+ "--format",
369
+ "--expand-tabs",
370
+ "--no-expand-tabs",
371
+ "--numbered",
372
+ "--email",
373
+ "--no-notes",
374
+ "--notes",
375
+ "--show-signature",
376
+ "--find-renames",
377
+ "--find-copies",
378
+ "--find-copies-harder",
379
+ "--break-rewrites",
380
+ "--no-renames",
381
+ "--irreversible-delete",
382
+ "--full-index",
383
+ "--binary",
384
+ "--text",
385
+ "--check",
386
+ "--exit-code",
387
+ "--word-diff",
388
+ "--function-context",
389
+ "--ignore-all-space",
390
+ "--ignore-space-change",
391
+ "--ignore-space-at-eol",
392
+ "--ignore-blank-lines",
393
+ "--ignore-cr-at-eol",
394
+ "--no-prefix",
395
+ "--default-prefix",
396
+ "--no-textconv",
397
+ "--no-ext-diff",
398
+ "--submodule",
399
+ "--ignore-submodules",
400
+ "--recurse-submodules",
401
+ "--no-recurse-submodules",
402
+ "--others",
403
+ "--deleted",
404
+ "--modified",
405
+ "--unmerged",
406
+ "--stage",
407
+ "--killed",
408
+ "--ignored",
409
+ "--exclude-standard",
410
+ "--error-unmatch",
411
+ "--full-name",
412
+ "--directory",
413
+ "--no-empty-directory",
414
+ "--untracked-files",
415
+ "--no-optional-locks",
416
+ "--no-pager",
417
+ "--paginate",
418
+ "--bare",
419
+ "--literal-pathspecs",
420
+ "--glob-pathspecs",
421
+ "--noglob-pathspecs",
422
+ "--icase-pathspecs",
423
+ "--line-number",
424
+ "--column",
425
+ "--heading",
426
+ "--no-index",
427
+ "--untracked",
428
+ "--no-recursive",
429
+ "--name-rev",
430
+ "--root",
431
+ "--dirty",
432
+ "--broken",
433
+ "--always",
434
+ "--merged",
435
+ "--no-merged",
436
+ "--show-toplevel",
437
+ "--show-cdup",
438
+ "--git-common-dir",
439
+ "--absolute-git-dir",
440
+ "--is-inside-work-tree",
441
+ "--is-inside-git-dir",
442
+ "--is-bare-repository",
443
+ "--symbolic",
444
+ "--symbolic-full-name",
445
+ "--verify",
446
+ "--short-hash",
447
+ "--textual",
448
+ "--batch",
449
+ "--batch-check",
450
+ "--batch-all-objects",
451
+ "--buffer",
452
+ "--follow-symlinks",
453
+ "--use-mailmap"
454
+ ],
455
+ values: [
456
+ "-n",
457
+ "-C",
458
+ "-B",
459
+ "-M",
460
+ "-L",
461
+ "-S",
462
+ "-G",
463
+ "-U",
464
+ "-e",
465
+ "-m",
466
+ "-f",
467
+ "-g",
468
+ "-x",
469
+ "-X",
470
+ "-W",
471
+ "--max-count",
472
+ "--skip",
473
+ "--since",
474
+ "--after",
475
+ "--until",
476
+ "--before",
477
+ "--author",
478
+ "--grep",
479
+ "--grep-reflog",
480
+ "--committer",
481
+ "--date",
482
+ "--diff-filter",
483
+ "--unified",
484
+ "--stat-width",
485
+ "--stat-name-width",
486
+ "--stat-count",
487
+ "--word-diff-regex",
488
+ "--line-prefix",
489
+ "--src-prefix",
490
+ "--dst-prefix",
491
+ "--pickaxe-regex",
492
+ "--anchored",
493
+ "--color-moved",
494
+ "--color-words",
495
+ "--inter-hunk-context",
496
+ "--max-parents",
497
+ "--min-parents",
498
+ "--simplify-by-decoration",
499
+ "--glob",
500
+ "--exclude",
501
+ "--sort",
502
+ "--contains",
503
+ "--points-at",
504
+ "--decorate-refs",
505
+ "--decorate-refs-exclude",
506
+ "--encoding",
507
+ "--context",
508
+ "--max-depth",
509
+ // `--filters` is absent from this list on purpose: it runs the clean and
510
+ // smudge filters the repository under research configures, which is the
511
+ // same execution vector as the configuration flag above with a different
512
+ // spelling.
513
+ "--and",
514
+ "--or",
515
+ "--not",
516
+ "--all-match",
517
+ "--threads",
518
+ "--path"
519
+ ],
520
+ patterns: [COUNT_FLAG]
521
+ },
522
+ wc: {
523
+ cluster: true,
524
+ booleans: [
525
+ "-c",
526
+ "-l",
527
+ "-m",
528
+ "-w",
529
+ "-L",
530
+ "--bytes",
531
+ "--chars",
532
+ "--lines",
533
+ "--words",
534
+ "--max-line-length",
535
+ "--total"
536
+ ]
537
+ },
538
+ du: {
539
+ cluster: true,
540
+ booleans: [
541
+ "-0",
542
+ "-a",
543
+ "-b",
544
+ "-c",
545
+ "-D",
546
+ "-h",
547
+ "-H",
548
+ "-k",
549
+ "-l",
550
+ "-L",
551
+ "-m",
552
+ "-P",
553
+ "-s",
554
+ "-S",
555
+ "-x",
556
+ "--all",
557
+ "--apparent-size",
558
+ "--bytes",
559
+ "--total",
560
+ "--dereference-args",
561
+ "--human-readable",
562
+ "--si",
563
+ "--inodes",
564
+ "--count-links",
565
+ "--dereference",
566
+ "--no-dereference",
567
+ "--separate-dirs",
568
+ "--summarize",
569
+ "--one-file-system",
570
+ "--null"
571
+ ],
572
+ values: [
573
+ "-d",
574
+ "-t",
575
+ "-B",
576
+ "-X",
577
+ "--max-depth",
578
+ "--threshold",
579
+ "--block-size",
580
+ "--exclude",
581
+ "--exclude-from",
582
+ "--time",
583
+ "--time-style",
584
+ "--files0-from"
585
+ ]
586
+ },
587
+ df: {
588
+ cluster: true,
589
+ booleans: [
590
+ "-a",
591
+ "-h",
592
+ "-H",
593
+ "-i",
594
+ "-k",
595
+ "-l",
596
+ "-m",
597
+ "-P",
598
+ "-T",
599
+ "-v",
600
+ "--all",
601
+ "--human-readable",
602
+ "--si",
603
+ "--inodes",
604
+ "--local",
605
+ "--portability",
606
+ "--print-type",
607
+ "--total",
608
+ "--sync",
609
+ "--no-sync"
610
+ ],
611
+ values: ["-B", "-t", "-x", "--block-size", "--type", "--exclude-type"]
612
+ },
613
+ file: {
614
+ cluster: true,
615
+ // `-C`/`--compile` is absent: it writes a compiled magic file.
616
+ booleans: [
617
+ "-b",
618
+ "-c",
619
+ "-h",
620
+ "-i",
621
+ "-k",
622
+ "-L",
623
+ "-n",
624
+ "-N",
625
+ "-p",
626
+ "-r",
627
+ "-s",
628
+ "-S",
629
+ "-v",
630
+ "-z",
631
+ "-0",
632
+ "--brief",
633
+ "--mime",
634
+ "--mime-type",
635
+ "--mime-encoding",
636
+ "--dereference",
637
+ "--no-dereference",
638
+ "--keep-going",
639
+ "--no-buffer",
640
+ "--no-pad",
641
+ "--raw",
642
+ "--special-files",
643
+ "--uncompress",
644
+ "--print0",
645
+ "--checking-printout"
646
+ ],
647
+ values: [
648
+ "-e",
649
+ "-m",
650
+ "-P",
651
+ "-F",
652
+ "--exclude",
653
+ "--magic-file",
654
+ "--separator",
655
+ "--parameter",
656
+ "--extension",
657
+ "--files-from"
658
+ ]
659
+ },
660
+ head: {
661
+ cluster: true,
662
+ booleans: ["-q", "-v", "-z", "--quiet", "--silent", "--verbose", "--zero-terminated"],
663
+ values: ["-c", "-n", "--bytes", "--lines"],
664
+ patterns: [COUNT_FLAG]
665
+ },
666
+ tail: {
667
+ cluster: true,
668
+ booleans: [
669
+ "-f",
670
+ "-F",
671
+ "-q",
672
+ "-v",
673
+ "-z",
674
+ "--follow",
675
+ "--retry",
676
+ "--quiet",
677
+ "--silent",
678
+ "--verbose",
679
+ "--zero-terminated"
680
+ ],
681
+ values: [
682
+ "-c",
683
+ "-n",
684
+ "-s",
685
+ "--bytes",
686
+ "--lines",
687
+ "--sleep-interval",
688
+ "--pid",
689
+ "--max-unchanged-stats"
690
+ ],
691
+ patterns: [COUNT_FLAG]
692
+ },
693
+ cat: {
694
+ cluster: true,
695
+ booleans: [
696
+ "-A",
697
+ "-b",
698
+ "-e",
699
+ "-E",
700
+ "-n",
701
+ "-s",
702
+ "-t",
703
+ "-T",
704
+ "-u",
705
+ "-v",
706
+ "--show-all",
707
+ "--number-nonblank",
708
+ "--show-ends",
709
+ "--number",
710
+ "--squeeze-blank",
711
+ "--show-tabs",
712
+ "--show-nonprinting"
713
+ ]
714
+ },
715
+ sort: {
716
+ cluster: true,
717
+ // Absent on purpose: `-o`/`--output` writes a file with no redirect for the
718
+ // redirect check to see, and `--compress-program` names a program that sort
719
+ // runs over its own temporary files — a permitted binary as an interpreter.
720
+ booleans: [
721
+ "-b",
722
+ "-c",
723
+ "-C",
724
+ "-d",
725
+ "-f",
726
+ "-g",
727
+ "-h",
728
+ "-i",
729
+ "-M",
730
+ "-n",
731
+ "-r",
732
+ "-R",
733
+ "-s",
734
+ "-u",
735
+ "-V",
736
+ "-z",
737
+ "--ignore-leading-blanks",
738
+ "--check",
739
+ "--dictionary-order",
740
+ "--ignore-case",
741
+ "--general-numeric-sort",
742
+ "--human-numeric-sort",
743
+ "--ignore-nonprinting",
744
+ "--month-sort",
745
+ "--numeric-sort",
746
+ "--random-sort",
747
+ "--reverse",
748
+ "--stable",
749
+ "--unique",
750
+ "--version-sort",
751
+ "--zero-terminated",
752
+ "--debug"
753
+ ],
754
+ values: [
755
+ "-k",
756
+ "-t",
757
+ "-S",
758
+ "-T",
759
+ "--key",
760
+ "--field-separator",
761
+ "--buffer-size",
762
+ "--temporary-directory",
763
+ "--random-source",
764
+ "--batch-size",
765
+ "--parallel",
766
+ "--sort"
767
+ ]
768
+ },
769
+ uniq: {
770
+ cluster: true,
771
+ booleans: [
772
+ "-c",
773
+ "-d",
774
+ "-D",
775
+ "-i",
776
+ "-u",
777
+ "-z",
778
+ "--count",
779
+ "--repeated",
780
+ "--all-repeated",
781
+ "--ignore-case",
782
+ "--unique",
783
+ "--zero-terminated"
784
+ ],
785
+ values: ["-f", "-s", "-w", "--skip-fields", "--skip-chars", "--check-chars", "--group"]
786
+ },
787
+ // No flag on either runs a program or writes a file, and the planner's
788
+ // commonest use of `echo` is a section marker — `echo --- files changed ---`
789
+ // is three tokens that all begin with a dash and none of them is a flag.
790
+ echo: { freeform: true },
791
+ printf: { freeform: true },
792
+ date: {
793
+ cluster: true,
794
+ // `-s`/`--set` is absent: it sets the system clock.
795
+ booleans: [
796
+ "-u",
797
+ "-R",
798
+ "-I",
799
+ "--utc",
800
+ "--universal",
801
+ "--rfc-email",
802
+ "--rfc-3339",
803
+ "--iso-8601",
804
+ "--debug"
805
+ ],
806
+ values: ["-d", "-f", "-r", "--date", "--file", "--reference"]
807
+ },
808
+ stat: {
809
+ cluster: true,
810
+ booleans: ["-f", "-L", "-t", "--file-system", "--terse", "--dereference"],
811
+ values: ["-c", "--format", "--printf", "--cached"]
812
+ },
813
+ basename: {
814
+ cluster: true,
815
+ booleans: ["-a", "-z", "--multiple", "--zero"],
816
+ values: ["-s", "--suffix"]
817
+ },
818
+ dirname: {
819
+ cluster: true,
820
+ booleans: ["-z", "--zero"]
821
+ },
822
+ realpath: {
823
+ cluster: true,
824
+ booleans: [
825
+ "-e",
826
+ "-m",
827
+ "-L",
828
+ "-P",
829
+ "-q",
830
+ "-s",
831
+ "-z",
832
+ "--canonicalize-existing",
833
+ "--canonicalize-missing",
834
+ "--logical",
835
+ "--physical",
836
+ "--quiet",
837
+ "--strip",
838
+ "--no-symlinks",
839
+ "--zero"
840
+ ],
841
+ values: ["--relative-to", "--relative-base"]
842
+ },
843
+ pwd: { cluster: true, booleans: ["-L", "-P", "--logical", "--physical"] },
844
+ which: { cluster: true, booleans: ["-a", "-s", "--all"] },
845
+ type: { cluster: true, booleans: ["-a", "-f", "-p", "-P", "-t"] },
846
+ uname: {
847
+ cluster: true,
848
+ booleans: [
849
+ "-a",
850
+ "-i",
851
+ "-m",
852
+ "-n",
853
+ "-o",
854
+ "-p",
855
+ "-r",
856
+ "-s",
857
+ "-v",
858
+ "--all",
859
+ "--kernel-name",
860
+ "--nodename",
861
+ "--kernel-release",
862
+ "--kernel-version",
863
+ "--machine",
864
+ "--processor",
865
+ "--hardware-platform",
866
+ "--operating-system"
867
+ ]
868
+ },
869
+ whoami: {},
870
+ cut: {
871
+ cluster: true,
872
+ // `--output-delimiter` names a separator string, not a file.
873
+ booleans: ["-n", "-s", "-z", "--only-delimited", "--complement", "--zero-terminated"],
874
+ values: [
875
+ "-b",
876
+ "-c",
877
+ "-d",
878
+ "-f",
879
+ "--bytes",
880
+ "--characters",
881
+ "--delimiter",
882
+ "--fields",
883
+ "--output-delimiter"
884
+ ]
885
+ },
886
+ nl: {
887
+ cluster: true,
888
+ booleans: ["-p", "--no-renumber"],
889
+ values: [
890
+ "-b",
891
+ "-d",
892
+ "-f",
893
+ "-h",
894
+ "-i",
895
+ "-l",
896
+ "-n",
897
+ "-s",
898
+ "-v",
899
+ "-w",
900
+ "--body-numbering",
901
+ "--section-delimiter",
902
+ "--footer-numbering",
903
+ "--header-numbering",
904
+ "--join-blank-lines",
905
+ "--line-increment",
906
+ "--number-format",
907
+ "--number-separator",
908
+ "--starting-line-number",
909
+ "--number-width"
910
+ ]
911
+ },
912
+ rg: {
913
+ cluster: true,
914
+ // `--pre` runs a named preprocessor over every file searched and
915
+ // `--hostname-bin` runs a named program to resolve the hostname; both are
916
+ // absent, and `--pre-glob` with them.
917
+ booleans: [
918
+ "-a",
919
+ "-c",
920
+ "-F",
921
+ "-h",
922
+ "-H",
923
+ "-i",
924
+ "-I",
925
+ "-l",
926
+ "-L",
927
+ "-n",
928
+ "-N",
929
+ "-o",
930
+ "-p",
931
+ "-P",
932
+ "-q",
933
+ "-s",
934
+ "-S",
935
+ "-u",
936
+ "-uu",
937
+ "-uuu",
938
+ "-U",
939
+ "-v",
940
+ "-V",
941
+ "-w",
942
+ "-x",
943
+ "-z",
944
+ "--text",
945
+ "--count",
946
+ "--count-matches",
947
+ "--fixed-strings",
948
+ "--files",
949
+ "--files-with-matches",
950
+ "--files-without-match",
951
+ "--follow",
952
+ "--hidden",
953
+ "--no-hidden",
954
+ "--ignore-case",
955
+ "--case-sensitive",
956
+ "--smart-case",
957
+ "--invert-match",
958
+ "--line-number",
959
+ "--no-line-number",
960
+ "--with-filename",
961
+ "--no-filename",
962
+ "--heading",
963
+ "--no-heading",
964
+ "--column",
965
+ "--no-column",
966
+ "--byte-offset",
967
+ "--vimgrep",
968
+ "--json",
969
+ "--stats",
970
+ "--trim",
971
+ "--pretty",
972
+ "--passthru",
973
+ "--word-regexp",
974
+ "--line-regexp",
975
+ "--multiline",
976
+ "--multiline-dotall",
977
+ "--no-multiline",
978
+ "--crlf",
979
+ "--null",
980
+ "--null-data",
981
+ "--binary",
982
+ "--search-zip",
983
+ "--no-ignore",
984
+ "--no-ignore-dot",
985
+ "--no-ignore-exclude",
986
+ "--no-ignore-files",
987
+ "--no-ignore-global",
988
+ "--no-ignore-parent",
989
+ "--no-ignore-vcs",
990
+ "--no-ignore-messages",
991
+ "--no-messages",
992
+ "--no-require-git",
993
+ "--require-git",
994
+ "--one-file-system",
995
+ "--pcre2",
996
+ "--no-pcre2",
997
+ "--auto-hybrid-regex",
998
+ "--line-buffered",
999
+ "--block-buffered",
1000
+ "--unicode",
1001
+ "--no-unicode",
1002
+ "--unrestricted",
1003
+ "--type-list",
1004
+ "--debug",
1005
+ "--trace",
1006
+ "--include-zero",
1007
+ "--no-context-separator",
1008
+ "--stop-on-nonmatch",
1009
+ "--quiet",
1010
+ "--no-binary"
1011
+ ],
1012
+ values: [
1013
+ "-A",
1014
+ "-B",
1015
+ "-C",
1016
+ "-d",
1017
+ "-e",
1018
+ "-E",
1019
+ "-f",
1020
+ "-g",
1021
+ "-j",
1022
+ "-m",
1023
+ "-M",
1024
+ "-r",
1025
+ "-t",
1026
+ "-T",
1027
+ "--after-context",
1028
+ "--before-context",
1029
+ "--context",
1030
+ "--context-separator",
1031
+ "--field-context-separator",
1032
+ "--field-match-separator",
1033
+ "--regexp",
1034
+ "--file",
1035
+ "--glob",
1036
+ "--iglob",
1037
+ "--glob-case-insensitive",
1038
+ "--type",
1039
+ "--type-not",
1040
+ "--type-add",
1041
+ "--type-clear",
1042
+ "--max-count",
1043
+ "--max-columns",
1044
+ "--max-depth",
1045
+ "--maxdepth",
1046
+ "--max-filesize",
1047
+ "--replace",
1048
+ "--threads",
1049
+ "--encoding",
1050
+ "--engine",
1051
+ "--sort",
1052
+ "--sortr",
1053
+ "--color",
1054
+ "--colors",
1055
+ "--path-separator",
1056
+ "--ignore-file",
1057
+ "--dfa-size-limit",
1058
+ "--regex-size-limit",
1059
+ "--binary-files",
1060
+ "--max-columns-preview"
1061
+ ]
1062
+ },
1063
+ grep: {
1064
+ cluster: true,
1065
+ // `-o` here is `--only-matching`, which writes nothing. It is the same two
1066
+ // characters as `sort -o`, which writes a file — the reason these sets are
1067
+ // per-binary rather than one shared list.
1068
+ booleans: [
1069
+ "-a",
1070
+ "-b",
1071
+ "-c",
1072
+ "-E",
1073
+ "-F",
1074
+ "-G",
1075
+ "-H",
1076
+ "-h",
1077
+ "-i",
1078
+ "-I",
1079
+ "-l",
1080
+ "-L",
1081
+ "-n",
1082
+ "-o",
1083
+ "-P",
1084
+ "-q",
1085
+ "-r",
1086
+ "-R",
1087
+ "-s",
1088
+ "-T",
1089
+ "-U",
1090
+ "-v",
1091
+ "-w",
1092
+ "-x",
1093
+ "-y",
1094
+ "-z",
1095
+ "-Z",
1096
+ "--extended-regexp",
1097
+ "--fixed-strings",
1098
+ "--basic-regexp",
1099
+ "--perl-regexp",
1100
+ "--ignore-case",
1101
+ "--no-ignore-case",
1102
+ "--invert-match",
1103
+ "--word-regexp",
1104
+ "--line-regexp",
1105
+ "--count",
1106
+ "--files-with-matches",
1107
+ "--files-without-match",
1108
+ "--line-number",
1109
+ "--with-filename",
1110
+ "--no-filename",
1111
+ "--only-matching",
1112
+ "--quiet",
1113
+ "--silent",
1114
+ "--no-messages",
1115
+ "--text",
1116
+ "--binary",
1117
+ "--line-buffered",
1118
+ "--null",
1119
+ "--null-data",
1120
+ "--byte-offset",
1121
+ "--recursive",
1122
+ "--dereference-recursive",
1123
+ "--initial-tab",
1124
+ "--unix-byte-offsets",
1125
+ "--no-group-separator",
1126
+ "--color",
1127
+ "--colour"
1128
+ ],
1129
+ values: [
1130
+ "-A",
1131
+ "-B",
1132
+ "-C",
1133
+ "-d",
1134
+ "-D",
1135
+ "-e",
1136
+ "-f",
1137
+ "-m",
1138
+ "--after-context",
1139
+ "--before-context",
1140
+ "--context",
1141
+ "--regexp",
1142
+ "--file",
1143
+ "--max-count",
1144
+ "--devices",
1145
+ "--directories",
1146
+ "--binary-files",
1147
+ "--include",
1148
+ "--exclude",
1149
+ "--exclude-dir",
1150
+ "--exclude-from",
1151
+ "--label",
1152
+ "--group-separator"
1153
+ ]
1154
+ },
1155
+ find: {
1156
+ // No clustering: find spells its flags as single-dash words, so walking the
1157
+ // characters of `-name` would read it as five short flags. And its arguments
1158
+ // are an expression, so `--` does not end them.
1159
+ expressionArgs: true,
1160
+ // Absent on purpose: `-exec`, `-execdir`, `-ok` and `-okdir` run an inner
1161
+ // command (also caught by the named guard below, which says so more
1162
+ // precisely), `-delete` removes files, and `-fprint`, `-fprint0`, `-fprintf`
1163
+ // and `-fls` are find's write family — the same write as `> file` with no
1164
+ // redirect for the redirect check to see.
1165
+ booleans: [
1166
+ "-H",
1167
+ "-L",
1168
+ "-P",
1169
+ "-a",
1170
+ "-and",
1171
+ "-o",
1172
+ "-or",
1173
+ "-not",
1174
+ "-d",
1175
+ "-depth",
1176
+ "-daystart",
1177
+ "-empty",
1178
+ "-executable",
1179
+ "-false",
1180
+ "-follow",
1181
+ "-help",
1182
+ "--help",
1183
+ "-ignore_readdir_race",
1184
+ "-noignore_readdir_race",
1185
+ "-ls",
1186
+ "-mount",
1187
+ "-nogroup",
1188
+ "-noleaf",
1189
+ "-nouser",
1190
+ "-print",
1191
+ "-print0",
1192
+ "-prune",
1193
+ "-quit",
1194
+ "-readable",
1195
+ "-true",
1196
+ "-version",
1197
+ "-writable",
1198
+ "-xdev"
1199
+ ],
1200
+ values: [
1201
+ "-maxdepth",
1202
+ "-mindepth",
1203
+ "-name",
1204
+ "-iname",
1205
+ "-path",
1206
+ "-ipath",
1207
+ "-wholename",
1208
+ "-iwholename",
1209
+ "-regex",
1210
+ "-iregex",
1211
+ "-lname",
1212
+ "-ilname",
1213
+ "-type",
1214
+ "-xtype",
1215
+ "-size",
1216
+ "-newer",
1217
+ "-anewer",
1218
+ "-cnewer",
1219
+ "-mtime",
1220
+ "-mmin",
1221
+ "-ctime",
1222
+ "-cmin",
1223
+ "-atime",
1224
+ "-amin",
1225
+ "-used",
1226
+ "-uid",
1227
+ "-gid",
1228
+ "-user",
1229
+ "-group",
1230
+ "-perm",
1231
+ "-links",
1232
+ "-inum",
1233
+ "-samefile",
1234
+ "-fstype",
1235
+ "-regextype",
1236
+ "-files0-from",
1237
+ "-printf",
1238
+ "-context",
1239
+ "-D"
1240
+ ],
1241
+ // `find -O2` glues the optimisation level on, and `-newermt`/`-newerat` and
1242
+ // friends spell the two comparison fields into the flag name.
1243
+ patterns: [/^-O\d$/, /^-newer[acmBt][acmBt]$/]
1244
+ },
1245
+ jq: {
1246
+ cluster: true,
1247
+ booleans: [
1248
+ "-a",
1249
+ "-c",
1250
+ "-C",
1251
+ "-e",
1252
+ "-h",
1253
+ "-j",
1254
+ "-M",
1255
+ "-n",
1256
+ "-r",
1257
+ "-R",
1258
+ "-s",
1259
+ "-S",
1260
+ "--ascii-output",
1261
+ "--compact-output",
1262
+ "--color-output",
1263
+ "--monochrome-output",
1264
+ "--exit-status",
1265
+ "--join-output",
1266
+ "--null-input",
1267
+ "--raw-input",
1268
+ "--raw-output",
1269
+ "--raw-output0",
1270
+ "--slurp",
1271
+ "--sort-keys",
1272
+ "--seq",
1273
+ "--stream",
1274
+ "--stream-errors",
1275
+ "--tab",
1276
+ "--unbuffered",
1277
+ "--args",
1278
+ "--jsonargs"
1279
+ ],
1280
+ values: [
1281
+ "-f",
1282
+ "-L",
1283
+ "--arg",
1284
+ "--argjson",
1285
+ "--slurpfile",
1286
+ "--rawfile",
1287
+ "--indent",
1288
+ "--from-file"
1289
+ ]
1290
+ },
1291
+ yq: {
1292
+ cluster: true,
1293
+ // `-i`/`--inplace`/`--in-place` rewrite the file, and `-s`/`--split-exp`
1294
+ // writes one file per document. All absent — and all bare or near-bare
1295
+ // booleans, which is why exempting booleans was not an option.
1296
+ booleans: [
1297
+ "-C",
1298
+ "-e",
1299
+ "-h",
1300
+ "-j",
1301
+ "-M",
1302
+ "-n",
1303
+ "-N",
1304
+ "-P",
1305
+ "-r",
1306
+ "-S",
1307
+ "-v",
1308
+ "-y",
1309
+ "-Y",
1310
+ "--colors",
1311
+ "--no-colors",
1312
+ "--exit-status",
1313
+ "--no-doc",
1314
+ "--null-input",
1315
+ "--prettyPrint",
1316
+ "--raw-output",
1317
+ "--sort-keys",
1318
+ "--tojson",
1319
+ "--yaml-output",
1320
+ "--yaml-roundtrip",
1321
+ "--unwrapScalar",
1322
+ "--header-preprocess",
1323
+ "--no-exit-status",
1324
+ "--verbose",
1325
+ "--xml-strict-mode"
1326
+ ],
1327
+ values: [
1328
+ "-I",
1329
+ "-o",
1330
+ "-p",
1331
+ "-L",
1332
+ "--indent",
1333
+ "--output-format",
1334
+ "--input-format",
1335
+ "--expression",
1336
+ "--width",
1337
+ "--xml-attribute-prefix",
1338
+ "--xml-content-name"
1339
+ ]
1340
+ }
1341
+ };
121
1342
  var GIT_READONLY_SUBCOMMANDS = [
122
1343
  "log",
123
1344
  "status",
124
1345
  "diff",
125
1346
  "show",
126
1347
  "ls-files",
127
- "ls-remote",
128
1348
  "ls-tree",
129
1349
  "branch",
130
1350
  "tag",
@@ -239,6 +1459,26 @@ var REFUSED_SUBCOMMANDS = {
239
1459
  az: ["login", "logout"],
240
1460
  terraform: ["apply", "destroy"]
241
1461
  };
1462
+ var SHELL_KEYWORDS = [
1463
+ "{",
1464
+ "}",
1465
+ "if",
1466
+ "then",
1467
+ "elif",
1468
+ "else",
1469
+ "fi",
1470
+ "for",
1471
+ "while",
1472
+ "until",
1473
+ "do",
1474
+ "done",
1475
+ "case",
1476
+ "esac",
1477
+ "select",
1478
+ "function",
1479
+ "time",
1480
+ "export"
1481
+ ];
242
1482
  var INTERPRETERS = [
243
1483
  "sh",
244
1484
  "bash",
@@ -682,16 +1922,79 @@ function pathLikeArgs(command, opts = {}) {
682
1922
  return !a.startsWith("-") && looksLikePath(a) ? [a] : [];
683
1923
  }));
684
1924
  }
1925
+ function matchFlag(spec, booleans, values, token) {
1926
+ if (token.startsWith("--")) {
1927
+ const eq = token.indexOf("=");
1928
+ const name = eq > 0 ? token.slice(0, eq) : token;
1929
+ if (booleans.has(name)) return "boolean";
1930
+ if (values.has(name)) return eq > 0 ? "boolean" : "value";
1931
+ return void 0;
1932
+ }
1933
+ if (booleans.has(token)) return "boolean";
1934
+ if (values.has(token)) return "value";
1935
+ if (spec.patterns?.some((p) => p.test(token))) return "boolean";
1936
+ if (!spec.cluster) return void 0;
1937
+ for (let i = 1; i < token.length; i++) {
1938
+ const short = `-${token[i]}`;
1939
+ if (booleans.has(short)) continue;
1940
+ if (values.has(short)) return i === token.length - 1 ? "value" : "boolean";
1941
+ return void 0;
1942
+ }
1943
+ return "boolean";
1944
+ }
1945
+ function takesNextToken(next) {
1946
+ if (next === void 0) return false;
1947
+ return !/^-./.test(next) || /^-\d/.test(next);
1948
+ }
1949
+ function flagLabel(token) {
1950
+ const eq = token.startsWith("--") ? token.indexOf("=") : -1;
1951
+ return eq > 0 ? token.slice(0, eq) : token;
1952
+ }
1953
+ function scanFlags(seg) {
1954
+ const spec = FLAG_POLICY[seg.binary];
1955
+ if (!spec || spec.freeform) return void 0;
1956
+ const booleans = /* @__PURE__ */ new Set([...spec.booleans ?? [], ...UNIVERSAL_FLAGS]);
1957
+ const values = new Set(spec.values ?? []);
1958
+ const operands = [];
1959
+ let i = 0;
1960
+ while (i < seg.args.length) {
1961
+ const token = seg.args[i];
1962
+ if (token === "--" && !spec.expressionArgs) {
1963
+ operands.push(...seg.args.slice(i + 1));
1964
+ break;
1965
+ }
1966
+ if (!/^-./.test(token)) {
1967
+ operands.push(token);
1968
+ i++;
1969
+ continue;
1970
+ }
1971
+ const shape = matchFlag(spec, booleans, values, token);
1972
+ if (shape === void 0) return { unknown: flagLabel(token), operands };
1973
+ i += shape === "value" && takesNextToken(seg.args[i + 1]) ? 2 : 1;
1974
+ }
1975
+ return { operands };
1976
+ }
1977
+ function subcommandOf(seg) {
1978
+ const scan = scanFlags(seg);
1979
+ if (scan) return scan.operands[0];
1980
+ return seg.args.find((a) => !a.startsWith("-"));
1981
+ }
1982
+ var SCOPE_LEAD_ARGS = 2;
685
1983
  function scopeFor(seg) {
686
1984
  if (!MULTIPLEXERS.includes(seg.binary)) return seg.binary;
687
- const sub = seg.args.find((a) => !a.startsWith("-"));
688
- return sub ? `${seg.binary} ${sub}` : seg.binary;
1985
+ const lead = [];
1986
+ for (const arg of seg.args) {
1987
+ if (arg.startsWith("-")) break;
1988
+ lead.push(arg);
1989
+ if (lead.length === SCOPE_LEAD_ARGS) break;
1990
+ }
1991
+ return [seg.binary, ...lead].join(" ");
689
1992
  }
690
1993
  function isAuto(seg) {
691
1994
  if (!AUTO_COMMANDS.includes(seg.binary)) return false;
692
1995
  if (seg.expandable) return false;
693
1996
  if (seg.binary !== "git") return true;
694
- const sub = seg.args.find((a) => !a.startsWith("-"));
1997
+ const sub = subcommandOf(seg);
695
1998
  return sub !== void 0 && GIT_READONLY_SUBCOMMANDS.includes(sub);
696
1999
  }
697
2000
  function refusalFor(seg) {
@@ -701,29 +2004,31 @@ function refusalFor(seg) {
701
2004
  if (seg.binary === "eval" || seg.binary === "exec") {
702
2005
  return `"${seg.binary}" runs a string as a command, which this classifier cannot inspect. Use the read-only research tools, or describe it as a task.`;
703
2006
  }
2007
+ if (seg.binary === "source" || seg.binary === ".") {
2008
+ return `"${seg.binary}" runs a file's contents as commands, which this classifier cannot inspect. Use the read-only research tools, or describe it as a task.`;
2009
+ }
2010
+ if (SHELL_KEYWORDS.includes(seg.binary)) {
2011
+ return `"${seg.binary}" is a shell keyword or compound-command opener. This classifier only inspects the command that runs first, and a keyword hides the real one from it. Run the inner command directly, or describe it as a task.`;
2012
+ }
704
2013
  const subs = REFUSED_SUBCOMMANDS[seg.binary];
705
2014
  if (subs) {
706
- const sub = seg.args.find((a) => !a.startsWith("-"));
2015
+ const sub = subcommandOf(seg);
707
2016
  if (sub && subs.includes(sub)) {
708
2017
  return `"${seg.binary} ${sub}" changes state or reaches outward. You are a read-only planner \u2014 put it in the plan and let the runner do it.`;
709
2018
  }
710
2019
  }
711
2020
  if (seg.binary === "git") {
712
- const sub = seg.args.find((a) => !a.startsWith("-"));
2021
+ const sub = subcommandOf(seg);
713
2022
  if (sub === "branch" || sub === "tag") {
714
2023
  const flag = seg.args.find((a) => ["-D", "-d", "-m", "-M", "--delete", "--move"].includes(a));
715
2024
  if (flag) {
716
2025
  return `"git ${sub} ${flag}" mutates refs. You are a read-only planner \u2014 describe the change as a task instead.`;
717
2026
  }
718
- }
719
- const execPathFlag = seg.args.find((a) => a === "--exec-path" || a.startsWith("--exec-path="));
720
- if (execPathFlag) {
721
- return `"git ${execPathFlag}" redirects where git looks for its helper programs, and can make it run an arbitrary one. Use the read-only research tools, or describe the change as a task.`;
722
- }
723
- if (sub === "grep") {
724
- const pagerFlag = seg.args.find((a) => a === "-O" || a === "--open-files-in-pager" || a.startsWith("--open-files-in-pager="));
725
- if (pagerFlag) {
726
- return `"git grep ${pagerFlag}" opens the results in an arbitrary named program. Use the read-only research tools, or describe the change as a task.`;
2027
+ const listing = seg.args.includes("-l") || seg.args.includes("--list");
2028
+ const scan2 = scanFlags(seg);
2029
+ const target = scan2?.operands[1];
2030
+ if (!listing && target) {
2031
+ return `"git ${sub} ${target}" creates or moves a ref. You are a read-only planner \u2014 describe the change as a task instead.`;
727
2032
  }
728
2033
  }
729
2034
  }
@@ -751,6 +2056,13 @@ function refusalFor(seg) {
751
2056
  if (seg.assignments.length > 0) {
752
2057
  return `The environment assignment "${seg.assignments[0]}" decides what "${seg.binary}" actually does, and this classifier cannot judge the value. Re-run the command without the assignment.`;
753
2058
  }
2059
+ const scan = scanFlags(seg);
2060
+ if (scan?.unknown) {
2061
+ return `"${scan.unknown}" is not a flag this classifier knows to be read-only on "${seg.binary}", and flags on otherwise read-only binaries can run a helper program or write a file. Re-run with read-only flags only, or describe the work as a task.`;
2062
+ }
2063
+ if (seg.binary === "uniq" && scan && scan.operands.length > 1) {
2064
+ return `"uniq" writes to its second file argument ("${scan.operands[1]}"). You are a read-only planner \u2014 pipe the output instead of naming an output file, or describe the write as a task.`;
2065
+ }
754
2066
  return void 0;
755
2067
  }
756
2068
  function withWrapperNote(reason, wrappers) {
@@ -1137,6 +2449,139 @@ It is outside the auto-allowed read-only set (${scope}). Continue with the read-
1137
2449
  }
1138
2450
  };
1139
2451
 
2452
+ // src/utils/redactSecrets.ts
2453
+ var REDACTION_MARKER = "REDACTED";
2454
+ function marker(kind, length) {
2455
+ return `[${REDACTION_MARKER} ${kind}: ${length} chars]`;
2456
+ }
2457
+ var LINE_PREFIX = String.raw`(?:[ \t]*\d+\||[^\s:]*:\d+:|)[ \t]*`;
2458
+ var LINE_PREFIX_ANCHORED = new RegExp("^" + LINE_PREFIX);
2459
+ var CREDENTIAL_WORD = String.raw`(?:api[_-]?keys?|secrets?|passwords?|passwd|passphrase|tokens?|access[_-]?keys?|private[_-]?keys?|client[_-]?secrets?|credentials?|auth)(?![a-z])`;
2460
+ var NAMED_CREDENTIAL = new RegExp(
2461
+ String.raw`((?:^|[^A-Za-z0-9_])["']?[A-Za-z0-9_.\-]*` + CREDENTIAL_WORD + String.raw`[A-Za-z0-9_.\-]*["']?[ \t]*(?:=>|[:=])[ \t]*)(["'\x60]?)([A-Za-z0-9_\-./+=:~!@#%^&*]{8,})\2`,
2462
+ "gi"
2463
+ );
2464
+ var DOTENV_CREDENTIAL = new RegExp(
2465
+ String.raw`^(` + LINE_PREFIX + String.raw`(?:export[ \t]+)?[A-Z0-9_]*` + CREDENTIAL_WORD.toUpperCase() + String.raw`[A-Z0-9_]*[ \t]*=[ \t]*)(["'\x60]?)([^\s"'\x60]{3,})\2[ \t]*$`,
2466
+ "gm"
2467
+ );
2468
+ var PROVIDER_KEY_PATTERNS = [
2469
+ // OpenAI / Anthropic / OpenRouter and the many `sk-` derivatives
2470
+ /(?<![A-Za-z0-9])sk-(?:ant-|proj-|or-v1-|live-|test-)?[A-Za-z0-9_-]{20,}/g,
2471
+ // GitHub personal-access, OAuth, app and refresh tokens
2472
+ /(?<![A-Za-z0-9])(?:gh[pousr]|github_pat)_[A-Za-z0-9_]{22,}/g,
2473
+ // Slack bot / user / app tokens
2474
+ /(?<![A-Za-z0-9])xox[abprse]-[A-Za-z0-9-]{10,}/g,
2475
+ // Stripe secret and restricted keys
2476
+ /(?<![A-Za-z0-9])[sr]k_(?:live|test)_[A-Za-z0-9]{16,}/g,
2477
+ // Google API keys and OAuth access tokens
2478
+ /(?<![A-Za-z0-9])AIza[A-Za-z0-9_-]{30,}/g,
2479
+ /(?<![A-Za-z0-9])ya29\.[A-Za-z0-9_-]{20,}/g,
2480
+ // npm, Hugging Face, Groq, GitLab, DigitalOcean, Shopify
2481
+ /(?<![A-Za-z0-9])(?:npm|hf|gsk|glpat|dop_v1|shpat|shpss)[_-][A-Za-z0-9]{20,}/g,
2482
+ /(?<![A-Za-z0-9])xai-[A-Za-z0-9]{20,}/g,
2483
+ // Three-segment JWTs, whose payload segment is base64 for `{"`
2484
+ /(?<![A-Za-z0-9])eyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g
2485
+ ];
2486
+ var CLOUD_ACCESS_KEY_ID = /(?<![A-Za-z0-9])(?:A3T[A-Z0-9]{2}|AKIA|ASIA|ABIA|ACCA|AGPA|AIDA|AIPA|ANPA|ANVA|AROA)[A-Z0-9]{16}(?![A-Za-z0-9])/g;
2487
+ var PEM_BEGIN = /-----BEGIN ((?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?)-----/;
2488
+ var PEM_BODY_LINE = /^[A-Za-z0-9+/=:,.\- \t]*$/;
2489
+ function redactPrivateKeyBlocks(text) {
2490
+ if (!PEM_BEGIN.test(text)) return text;
2491
+ const out = [];
2492
+ let label = null;
2493
+ let bodyChars = 0;
2494
+ const closeBlock = () => {
2495
+ if (label === null) return;
2496
+ out.push(marker("private key", bodyChars));
2497
+ label = null;
2498
+ bodyChars = 0;
2499
+ };
2500
+ for (const line of text.split("\n")) {
2501
+ if (label === null) {
2502
+ out.push(line);
2503
+ const begun = PEM_BEGIN.exec(line);
2504
+ if (begun) label = begun[1];
2505
+ continue;
2506
+ }
2507
+ const content = line.replace(LINE_PREFIX_ANCHORED, "");
2508
+ if (content.includes(`-----END ${label}-----`)) {
2509
+ closeBlock();
2510
+ out.push(line);
2511
+ } else if (PEM_BODY_LINE.test(content)) {
2512
+ bodyChars += content.trim().length;
2513
+ } else {
2514
+ closeBlock();
2515
+ out.push(line);
2516
+ }
2517
+ }
2518
+ closeBlock();
2519
+ return out.join("\n");
2520
+ }
2521
+ var NUMERIC = /^[-+]?\d+(?:\.\d+)?$/;
2522
+ var IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
2523
+ var MEMBER_EXPRESSION = /^[A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_$][A-Za-z0-9_$]*)+$/;
2524
+ var SLUG = /^[a-z]+(?:[-_][a-z]+)+$/;
2525
+ var PATHISH = /^(?:\.{0,2}\/|~\/)/;
2526
+ var URLISH = /^[a-z][a-z0-9+.-]*:\/\//i;
2527
+ var CODE_KEYWORDS = /* @__PURE__ */ new Set([
2528
+ "true",
2529
+ "false",
2530
+ "null",
2531
+ "none",
2532
+ "nil",
2533
+ "undefined",
2534
+ "void",
2535
+ "string",
2536
+ "number",
2537
+ "boolean",
2538
+ "object",
2539
+ "any",
2540
+ "unknown",
2541
+ "bool",
2542
+ "str",
2543
+ "int"
2544
+ ]);
2545
+ var UNAMBIGUOUS_CREDENTIAL_NAME = /(?:password|passwd|passphrase|secret|credentials?|private[_-]?key)/i;
2546
+ function looksLikeKeyMaterial(value) {
2547
+ const hasDigit = /\d/.test(value);
2548
+ const hasAlpha = /[A-Za-z]/.test(value);
2549
+ const mixedCase = /[a-z]/.test(value) && /[A-Z]/.test(value);
2550
+ return hasDigit && hasAlpha || mixedCase || value.length >= 24;
2551
+ }
2552
+ function looksLikeCode(value) {
2553
+ return NUMERIC.test(value) || CODE_KEYWORDS.has(value.toLowerCase()) || MEMBER_EXPRESSION.test(value) || PATHISH.test(value) || URLISH.test(value);
2554
+ }
2555
+ function trimTrailingPunctuation(value) {
2556
+ const match = /[.:!]+$/.exec(value);
2557
+ if (!match || match.index === 0) return { value, tail: "" };
2558
+ return { value: value.slice(0, match.index), tail: match[0] };
2559
+ }
2560
+ function redactSecrets(text) {
2561
+ if (!text) return text;
2562
+ let out = redactPrivateKeyBlocks(text);
2563
+ out = out.replace(DOTENV_CREDENTIAL, (full, head, quote, value) => {
2564
+ if (looksLikeCode(value) || value.startsWith("$")) return full;
2565
+ return `${head}${quote}${marker("credential", value.length)}${quote}`;
2566
+ });
2567
+ out = out.replace(NAMED_CREDENTIAL, (full, head, quote, raw) => {
2568
+ const { value, tail } = trimTrailingPunctuation(raw);
2569
+ if (value.length < 8) return full;
2570
+ if (looksLikeCode(value) || value.startsWith("$")) return full;
2571
+ if (UNAMBIGUOUS_CREDENTIAL_NAME.test(head) && /\d/.test(value)) {
2572
+ return `${head}${quote}${marker("credential", value.length)}${quote}${tail}`;
2573
+ }
2574
+ if (IDENTIFIER.test(value) || SLUG.test(value)) return full;
2575
+ if (!UNAMBIGUOUS_CREDENTIAL_NAME.test(head) && !looksLikeKeyMaterial(value)) return full;
2576
+ return `${head}${quote}${marker("credential", value.length)}${quote}${tail}`;
2577
+ });
2578
+ for (const pattern of PROVIDER_KEY_PATTERNS) {
2579
+ out = out.replace(pattern, (hit) => marker("api key", hit.length));
2580
+ }
2581
+ out = out.replace(CLOUD_ACCESS_KEY_ID, (hit) => marker("access key id", hit.length));
2582
+ return out;
2583
+ }
2584
+
1140
2585
  // src/services/executeTool.ts
1141
2586
  var GREP_OUTPUT_MODES = ["content", "files", "count"];
1142
2587
  function num(value) {
@@ -1163,6 +2608,10 @@ function grepOptions(args) {
1163
2608
  }
1164
2609
  var STOPPED_TOOL_RESULT = "Stopped by the user \u2014 this call was not executed, and no further tool calls will run this turn.";
1165
2610
  async function executeTool(name, args, fs10, fetcher, signal) {
2611
+ const outcome = await runTool(name, args, fs10, fetcher, signal);
2612
+ return { ...outcome, output: redactSecrets(outcome.output) };
2613
+ }
2614
+ async function runTool(name, args, fs10, fetcher, signal) {
1166
2615
  if (signal?.aborted) return { success: false, output: STOPPED_TOOL_RESULT, truncated: false };
1167
2616
  switch (name) {
1168
2617
  case "read_file": {
@@ -4498,11 +5947,11 @@ function composeAugmentedPrompt(task, allTasks, opts) {
4498
5947
  if (isHitlTask(task)) {
4499
5948
  blocks.push(renderCheckpointInstruction());
4500
5949
  }
4501
- const marker = renderCompletionMarker(task);
4502
- if (blocks.length === 0) return basePrompt + marker;
5950
+ const marker2 = renderCompletionMarker(task);
5951
+ if (blocks.length === 0) return basePrompt + marker2;
4503
5952
  return `${blocks.join("\n\n")}
4504
5953
 
4505
- ${basePrompt}${marker}`;
5954
+ ${basePrompt}${marker2}`;
4506
5955
  }
4507
5956
 
4508
5957
  // src/services/VerdictEngine.ts
@@ -7449,7 +8898,7 @@ function assertWorkspaceIsProject(workspace, deps = {}) {
7449
8898
  throw new WorkspaceNotAProjectError(workspace);
7450
8899
  }
7451
8900
  const hasMarker = [...PROJECT_MARKER_DIRS, ...PROJECT_MARKER_FILES].some(
7452
- (marker) => exists(path9.join(resolved, marker))
8901
+ (marker2) => exists(path9.join(resolved, marker2))
7453
8902
  );
7454
8903
  if (!hasMarker) throw new WorkspaceNotAProjectError(workspace);
7455
8904
  }
@@ -8669,7 +10118,8 @@ var CliAgentAiService = class {
8669
10118
  let stepIndex = 0;
8670
10119
  const truncate = (value) => value.length > LOG_MAX_CHARS ? `${value.slice(0, LOG_MAX_CHARS)}
8671
10120
  [... truncated, total ${value.length} chars]` : value;
8672
- const settle = (id, output, success, outcome) => {
10121
+ const settle = (id, rawOutput, success, outcome) => {
10122
+ const output = redactSecrets(rawOutput);
8673
10123
  const call = pendingCalls.get(id);
8674
10124
  pendingCalls.delete(id);
8675
10125
  const step = {
@@ -9200,6 +10650,16 @@ function ensureDir(dir) {
9200
10650
  function getStateDir(baseDir) {
9201
10651
  return path10.join(baseDir ?? process.cwd(), STATE_DIR);
9202
10652
  }
10653
+ var IGNORE_FILE = ".gitignore";
10654
+ function ensureStateDirIgnored(baseDir) {
10655
+ const dir = getStateDir(baseDir);
10656
+ ensureDir(dir);
10657
+ try {
10658
+ fs4.writeFileSync(path10.join(dir, IGNORE_FILE), "*\n", { flag: "wx" });
10659
+ } catch (err) {
10660
+ if (err.code !== "EEXIST") throw err;
10661
+ }
10662
+ }
9203
10663
 
9204
10664
  // src/utils/sessionId.ts
9205
10665
  import { randomBytes } from "crypto";
@@ -9229,6 +10689,7 @@ function sessionFilename(session) {
9229
10689
  function saveSession(plan, goal, baseDir, id) {
9230
10690
  const sessionsDir = getSessionsDir(baseDir);
9231
10691
  ensureDir(sessionsDir);
10692
+ ensureStateDirIgnored(baseDir);
9232
10693
  const now = (/* @__PURE__ */ new Date()).toISOString();
9233
10694
  const meta = {
9234
10695
  id: id || mintSessionId(),
@@ -10781,7 +12242,7 @@ import { execFile as execFile2 } from "child_process";
10781
12242
  import { promisify as promisify4 } from "util";
10782
12243
  import { tmpdir } from "os";
10783
12244
  import { join as join10 } from "path";
10784
- import { existsSync as existsSync6, readFileSync as readFileSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "fs";
12245
+ import { existsSync as existsSync6, readFileSync as readFileSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync6 } from "fs";
10785
12246
 
10786
12247
  // src/utils/tmux.ts
10787
12248
  import { execSync as execSync2 } from "child_process";
@@ -10872,7 +12333,7 @@ var TmuxSession = class extends AbstractTerminalSession {
10872
12333
  * lets the user keep poking at a finished task's terminal.
10873
12334
  */
10874
12335
  async start(command, args, cwd, env) {
10875
- writeFileSync5(this.logPath, "");
12336
+ writeFileSync6(this.logPath, "");
10876
12337
  const envAssignments = Object.entries(env ?? {}).map(([k, v]) => `${k}=${posixShellQuote(v)}`).join(" ");
10877
12338
  const inner = [command, ...args].map(posixShellQuote).join(" ");
10878
12339
  const prefix = envAssignments ? `env ${envAssignments} ` : "";
@@ -12245,6 +13706,7 @@ export {
12245
13706
  emptyWarnings,
12246
13707
  enabledRunners,
12247
13708
  ensureDir,
13709
+ ensureStateDirIgnored,
12248
13710
  escapeControlCharsInStrings,
12249
13711
  executionLogBlock,
12250
13712
  executionSummary,