@lazyingart/agintiflow 0.20.221 → 0.20.223

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.221",
3
+ "version": "0.20.223",
4
4
  "type": "module",
5
5
  "description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
6
6
  "license": "Apache-2.0",
@@ -658,6 +658,21 @@ try {
658
658
  exactCompoundReadOnlyAuditPolicy.writesWorkspace === false,
659
659
  "a finite read-only prelude and conditional directory audit incorrectly required destructive host permission"
660
660
  );
661
+ const exactReadOnlyExistenceLoopWithSuffix =
662
+ 'for f in scripts/xyq_cdp_browser.py scripts/xyq_chrome/launch_chrome.sh scripts/xyq_chrome/watch_thread_dom_download.py scripts/musia_mv_finalize.sh; do test -e "/home/lachlan/ProjectsLFS/LALACHAN/$f" && echo "OK $f" || echo "MISS $f"; done; echo "---mv_packs---"; ls -1 /home/lachlan/ProjectsLFS/Musia/data/mv_packs 2>&1; echo "---creative_projects---"; ls -1 /home/lachlan/ProjectsLFS/Musia/data/creative_projects 2>&1 | head -20';
663
+ const exactReadOnlyExistenceLoopWithSuffixPolicy = evaluateCommandPolicy(
664
+ exactReadOnlyExistenceLoopWithSuffix,
665
+ hostReadRootPolicy
666
+ );
667
+ assert(
668
+ exactReadOnlyExistenceLoopWithSuffixPolicy.allowed &&
669
+ exactReadOnlyExistenceLoopWithSuffixPolicy.category === "read-only" &&
670
+ exactReadOnlyExistenceLoopWithSuffixPolicy.boundedForLoop === true &&
671
+ exactReadOnlyExistenceLoopWithSuffixPolicy.boundedCompoundSequence === true &&
672
+ exactReadOnlyExistenceLoopWithSuffixPolicy.needsNetwork === false &&
673
+ exactReadOnlyExistenceLoopWithSuffixPolicy.writesWorkspace === false,
674
+ "a finite read-only existence loop with bounded list suffix incorrectly required destructive host permission"
675
+ );
661
676
  const exactReadOnlyGitIdentityProbe =
662
677
  'git status --short && echo "TOPLEVEL=$(git rev-parse --show-toplevel 2>&1)" && echo "BRANCH=$(git rev-parse --abbrev-ref HEAD 2>&1)"';
663
678
  const exactReadOnlyGitIdentityProbePolicy = evaluateCommandPolicy(
@@ -685,6 +700,20 @@ try {
685
700
  exactReadOnlyGitIdentityLoopPolicy.writesWorkspace === false,
686
701
  "bounded per-repository Git identity/status audit incorrectly required destructive host permission"
687
702
  );
703
+ const exactReadOnlyGitRepositoryAudit =
704
+ 'for d in /home/lachlan/ProjectsLFS/AgenticApp /home/lachlan/ProjectsLFS/Musia /home/lachlan/ProjectsLFS/LALACHAN /home/lachlan/DiskMech/Projects/lazyedit; do echo "=== $d ==="; git -C "$d" config --get user.name; git -C "$d" config --get user.email; git -C "$d" status --porcelain=v1 --branch; echo "--- remotes ---"; git -C "$d" remote -v; echo; done';
705
+ const exactReadOnlyGitRepositoryAuditPolicy = evaluateCommandPolicy(
706
+ exactReadOnlyGitRepositoryAudit,
707
+ hostReadRootPolicy
708
+ );
709
+ assert(
710
+ exactReadOnlyGitRepositoryAuditPolicy.allowed &&
711
+ exactReadOnlyGitRepositoryAuditPolicy.category === "read-only" &&
712
+ exactReadOnlyGitRepositoryAuditPolicy.boundedForLoop === true &&
713
+ exactReadOnlyGitRepositoryAuditPolicy.needsNetwork === false &&
714
+ exactReadOnlyGitRepositoryAuditPolicy.writesWorkspace === false,
715
+ "bounded per-repository Git identity/status/remote audit incorrectly required destructive host permission"
716
+ );
688
717
  for (const unsafeEchoSubstitution of [
689
718
  'echo "VALUE=$(rm -rf report.md)"',
690
719
  'echo "VALUE=$(cp README.md report.md)"',
@@ -716,6 +745,8 @@ try {
716
745
  'for d in /tmp; do echo "VALUE=$(git -C "$d" config --global user.name)"; done',
717
746
  'for d in /tmp; do echo "VALUE=$(git -C "$d" status; rm -rf report.md)"; done',
718
747
  'for d in /tmp; do git -C "$d" status --short > report.md; done',
748
+ 'for d in /tmp; do git -C "$d" remote add origin https://example.com/repo.git; done',
749
+ 'for d in /tmp; do git -C "$d" remote set-url origin https://example.com/repo.git; done',
719
750
  ]) {
720
751
  const unsafeLoopSubstitutionPolicy = evaluateCommandPolicy(
721
752
  unsafeLoopSubstitution,
@@ -738,6 +769,9 @@ try {
738
769
  'echo start; for d in /tmp; do if [ -d "$d" ]; then if [ -f "$d/x" ]; then cat "$d/x"; else echo missing; fi; else echo absent; fi; done',
739
770
  'echo start; for d in $ROOTS; do if [ -d "$d" ]; then ls "$d"; else echo missing; fi; done',
740
771
  'echo start; for d in /tmp; do if [ -d "$d" ]; then ls "$d"; else echo missing; fi; done; rm -rf report.md',
772
+ 'for f in README.md; do test -e "$f" && echo ok || echo missing; done; cp README.md copied.md',
773
+ 'for f in README.md; do test -e "$f" && echo ok || echo missing; done; curl https://example.com',
774
+ 'for f in README.md; do test -e "$f" && echo ok || echo missing; done; echo done > report.md',
741
775
  ]) {
742
776
  const unsafeCompoundAuditPolicy = evaluateCommandPolicy(unsafeCompoundAudit, hostReadRootPolicy);
743
777
  assert(
@@ -1158,7 +1158,8 @@ function classifyScopedReadOnlyGitProbe(normalized = "") {
1158
1158
  (args.length === 1 && ["user.name", "user.email"].includes(args[0])) ||
1159
1159
  (args.length === 2 && args[0] === "--get" && ["user.name", "user.email"].includes(args[1]))
1160
1160
  );
1161
- if (!safeStatus && !safeIdentityConfig) return null;
1161
+ const safeRemoteList = operation === "remote" && args.length === 1 && args[0] === "-v";
1162
+ if (!safeStatus && !safeIdentityConfig && !safeRemoteList) return null;
1162
1163
 
1163
1164
  return {
1164
1165
  category: "read-only",
@@ -1356,7 +1357,11 @@ function classifySimpleCommand(normalized) {
1356
1357
  };
1357
1358
  }
1358
1359
 
1359
- if (matchAny(READ_ONLY_PATTERNS, commandForPatternMatching) || isReadOnlyFindCommand(normalized)) {
1360
+ if (
1361
+ matchAny(READ_ONLY_PATTERNS, commandForPatternMatching) ||
1362
+ isReadOnlyFindCommand(normalized) ||
1363
+ (!hasActiveShellExpansion(benignRedirectCommand) && isReadOnlyShellCondition(benignRedirectCommand))
1364
+ ) {
1360
1365
  return {
1361
1366
  category: "read-only",
1362
1367
  needsNetwork: false,
@@ -1766,7 +1771,7 @@ function classifyReadOnlyForLoop(normalized) {
1766
1771
 
1767
1772
  function classifyReadOnlyCompoundSequence(normalized) {
1768
1773
  const text = String(normalized || "").trim();
1769
- if (!text || /^for\s+/.test(text) || hasActiveShellCommandSubstitution(text)) return null;
1774
+ if (!text || hasActiveShellCommandSubstitution(text)) return null;
1770
1775
 
1771
1776
  let forIndex = findUnquotedShellWord(text, "for");
1772
1777
  const startsAtCommandBoundary = (index) => {
@@ -1777,16 +1782,24 @@ function classifyReadOnlyCompoundSequence(normalized) {
1777
1782
  while (forIndex > 0 && !startsAtCommandBoundary(forIndex)) {
1778
1783
  forIndex = findUnquotedShellWord(text, "for", forIndex + 3);
1779
1784
  }
1780
- if (forIndex <= 0) return null;
1785
+ if (forIndex < 0) return null;
1781
1786
 
1782
1787
  const prefixSource = trimShellListBoundary(text.slice(0, forIndex));
1783
- const loopSource = text.slice(forIndex).trim();
1784
- const prefixClassification = classifyReadOnlyCommandList(prefixSource);
1785
- if (prefixClassification.category === "blocked" || prefixClassification.category === "destructive") {
1786
- return { ...prefixClassification, gitOnly: false };
1787
- }
1788
- if (prefixClassification.category !== "read-only" || prefixClassification.writesWorkspace) {
1789
- return broadForLoopClassification(text, "the command prelude is not read-only");
1788
+ const doneIndex = findUnquotedShellWord(text, "done", forIndex + 3);
1789
+ if (doneIndex < 0) return null;
1790
+ const loopSource = text.slice(forIndex, doneIndex + 4).trim();
1791
+ const suffixSource = trimShellListBoundary(text.slice(doneIndex + 4));
1792
+ if (!prefixSource && !suffixSource) return null;
1793
+
1794
+ for (const [label, source] of [["prelude", prefixSource], ["suffix", suffixSource]]) {
1795
+ if (!source) continue;
1796
+ const classification = classifyReadOnlyCommandList(source);
1797
+ if (classification.category === "blocked" || classification.category === "destructive") {
1798
+ return { ...classification, gitOnly: false };
1799
+ }
1800
+ if (classification.category !== "read-only" || classification.writesWorkspace) {
1801
+ return broadForLoopClassification(text, `the command ${label} is not read-only`);
1802
+ }
1790
1803
  }
1791
1804
  const loopClassification = classifyReadOnlyForLoop(loopSource);
1792
1805
  if (!loopClassification) return null;
@@ -1800,7 +1813,7 @@ function classifyReadOnlyCompoundSequence(normalized) {
1800
1813
  gitOnly: false,
1801
1814
  boundedForLoop: true,
1802
1815
  boundedCompoundSequence: true,
1803
- reason: "Finite read-only command prelude followed by a bounded read-only shell loop.",
1816
+ reason: "A bounded read-only shell loop has only finite read-only command context.",
1804
1817
  };
1805
1818
  }
1806
1819