@codyswann/lisa 4.72.0 → 4.72.2
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/all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh +769 -37
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +4 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +14 -5
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/opencode/plugin-templates/parity-safety-net.sh +769 -37
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-acceptance-criteria/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-agent-design-best-practices/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-agent-ready/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-analyze-claude-remote/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-atlassian-access/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-attribute-failure/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-automation-status/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-bug-triage/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-capability-drift/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-codebase-research/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-codify-verification/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-confluence-prd-intake/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-confluence-to-tracker/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-confluence-write-prd/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-cross-pollinate/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-debrief/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-debrief-apply/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-delivery-effectiveness/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-design-intake/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-detect-tooling/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-doctor/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-drive-pr-to-merge/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-epic-triage/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-evaluation-suite/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-exploratory-qa/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-fix-linter-error/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-generate-claude-remote-build-script/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-git-commit/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-git-prune/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-git-submit-pr/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-github-add-journey/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-github-build-intake/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-github-claim/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-github-create/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-github-evidence/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-github-journey/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-github-prd-intake/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-github-project-v2/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-github-read-issue/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-github-sync/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-github-to-tracker/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-github-validate-issue/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-github-verify/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-github-write-issue/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-github-write-prd/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-health/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-health-drift-cron/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-implement/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-improve-code-complexity/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-improve-harness/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-improve-max-lines/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-improve-max-lines-per-function/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-improve-test-coverage/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-improve-tests/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-intake/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-intake-explain/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-jam-access/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-add-journey/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-build-intake/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-claim/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-create/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-evidence/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-journey/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-read-ticket/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-sync/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-validate-ticket/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-verify/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-write-ticket/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-jsdoc-best-practices/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-kane-browser/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-learnings-audit/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-access/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-add-journey/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-build-intake/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-claim/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-create/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-evidence/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-journey/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-prd-intake/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-read-issue/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-sync/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-to-tracker/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-validate-issue/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-verify/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-write-issue/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-write-prd/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-monitor/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-nightly-add-test-coverage/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-nightly-improve-tests/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-nightly-lower-code-complexity/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-notion-access/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-notion-prd-intake/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-notion-to-tracker/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-notion-write-prd/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-parity-code-review/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-parity-code-simplifier/SKILL.md +3 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-parity-code-simplifier/agents/openai.yaml +2 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-parity-coderabbit/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-parity-sentry-sdk-setup/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-parity-sentry-seer/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-parity-skill-creator/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-performance-review/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-persist-learning/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-plan/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-plugin-sync-explain/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-posthog-access/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-prd-backlink/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-prd-source-write/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-prd-ticket-coverage/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-product-walkthrough/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-project-ideation/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-pull-request-review/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-quality-review/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-queue-status/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-remote-dispatch/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-repair-intake/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-reproduce-bug/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-research/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-review-implementation/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-review-local/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-rework-triage/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-root-cause-analysis/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-security-review/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-security-zap-scan/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-sentry-access/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-atlassian/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-automations/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-confluence/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-github/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-github-repo/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-jira/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-kane/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-linear/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-local-env/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-notion/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-aws/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-sonar/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-workstation/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-sonarcloud-access/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-spec-conformance/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-starter-sync/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-sync-down/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-task-decomposition/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-task-triage/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-tdd-implementation/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-tear-down-automations/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-test-strategy/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-ticket-triage/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-track/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-tracker-add-journey/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-tracker-build-intake/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-tracker-claim/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-tracker-create/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-tracker-evidence/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-tracker-journey/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-tracker-read/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-tracker-source-artifacts/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-tracker-sync/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-tracker-validate/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-tracker-verify/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-tracker-write/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-usage-accounting/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-use-the-product/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-validate-tracker-mapping/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-verification-lifecycle/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-verify/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-verify-prd/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-verify-workflow-change/SKILL.md +2 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-wiki-install/SKILL.md +2 -0
- package/plugins/lisa/hooks/parity-safety-net.sh +769 -37
- package/plugins/lisa/rules/reference/design-source-of-truth.md +1 -1
- package/plugins/lisa/rules/reference/rejection-detection.md +1 -1
- package/plugins/lisa-agy/hooks/parity-safety-net.sh +769 -37
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/skills/lisa-setup-aws/SKILL.md +3 -1
- package/plugins/lisa-cdk/.codex-plugin/skills/lisa-setup-aws/agents/openai.yaml +2 -2
- package/plugins/lisa-cdk/.codex-plugin/skills/setup-aws-accounts/SKILL.md +2 -0
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/hooks/parity-safety-net.sh +769 -37
- package/plugins/lisa-copilot/rules/reference/design-source-of-truth.md +1 -1
- package/plugins/lisa-copilot/rules/reference/rejection-detection.md +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/parity-safety-net.sh +769 -37
- package/plugins/lisa-cursor/rules/design-source-of-truth-reference.mdc +1 -1
- package/plugins/lisa-cursor/rules/rejection-detection-reference.mdc +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/skills/add-app-clip/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/apollo-client/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/atomic-design-gluestack/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/building-native-ui/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/container-view-pattern/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/cross-platform-compatibility/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/directory-structure/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/e2e-coverage-gaps/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/eas-update-insights/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/expo-api-routes/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/expo-brownfield/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/expo-cicd-workflows/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/expo-deployment/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/expo-dev-client/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/expo-env-config/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/expo-module/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/expo-router-best-practices/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/expo-tailwind-setup/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/expo-ui-jetpack-compose/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/expo-ui-swift-ui/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/gluestack-nativewind/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/jira-add-journey/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/jira-create/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/jira-evidence/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/jira-journey/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/jira-verify/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/lisa-e2e-coverage-gaps/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/lisa-maestro-mcp-setup/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/local-state/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/maestro-mcp-setup/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/native-data-fetching/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/ops-browser-uat/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/ops-check-logs/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/ops-db-ops/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/ops-deploy/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/ops-monitor-errors/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/ops-performance/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/ops-run-local/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/ops-verify-health/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/owasp-zap/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/play-store-access/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/playwright-ci-debugging/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/playwright-selectors/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/reduce-complexity/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/testing-library/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/upgrading-expo/SKILL.md +2 -0
- package/plugins/lisa-expo/.codex-plugin/skills/use-dom/SKILL.md +2 -0
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/skills/e2e-coverage-gaps/SKILL.md +2 -0
- package/plugins/lisa-harper-fabric/.codex-plugin/skills/harper-auth/SKILL.md +2 -0
- package/plugins/lisa-harper-fabric/.codex-plugin/skills/harper-build-and-deploy/SKILL.md +2 -0
- package/plugins/lisa-harper-fabric/.codex-plugin/skills/harper-caching/SKILL.md +2 -0
- package/plugins/lisa-harper-fabric/.codex-plugin/skills/harper-component-model/SKILL.md +2 -0
- package/plugins/lisa-harper-fabric/.codex-plugin/skills/harper-config-yaml/SKILL.md +2 -0
- package/plugins/lisa-harper-fabric/.codex-plugin/skills/harper-operations/SKILL.md +2 -0
- package/plugins/lisa-harper-fabric/.codex-plugin/skills/harper-realtime/SKILL.md +2 -0
- package/plugins/lisa-harper-fabric/.codex-plugin/skills/harper-resources/SKILL.md +2 -0
- package/plugins/lisa-harper-fabric/.codex-plugin/skills/harper-rest-queries/SKILL.md +2 -0
- package/plugins/lisa-harper-fabric/.codex-plugin/skills/harper-schema-graphql/SKILL.md +3 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/skills/harper-schema-graphql/agents/openai.yaml +2 -2
- package/plugins/lisa-harper-fabric/.codex-plugin/skills/harper-testing/SKILL.md +2 -0
- package/plugins/lisa-harper-fabric/.codex-plugin/skills/lisa-e2e-coverage-gaps/SKILL.md +2 -0
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/skills/nestjs-graphql/SKILL.md +2 -0
- package/plugins/lisa-nestjs/.codex-plugin/skills/nestjs-rules/SKILL.md +2 -0
- package/plugins/lisa-nestjs/.codex-plugin/skills/typeorm-patterns/SKILL.md +2 -0
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/skills/lisa-connect-repo-topic/SKILL.md +2 -0
- package/plugins/lisa-openclaw/.codex-plugin/skills/lisa-connect-staff-slack/SKILL.md +2 -0
- package/plugins/lisa-openclaw/.codex-plugin/skills/lisa-connect-staff-telegram/SKILL.md +2 -0
- package/plugins/lisa-openclaw/.codex-plugin/skills/lisa-openclaw-connect-repo-topic/SKILL.md +2 -0
- package/plugins/lisa-openclaw/.codex-plugin/skills/lisa-openclaw-connect-staff/SKILL.md +2 -0
- package/plugins/lisa-openclaw/.codex-plugin/skills/lisa-openclaw-setup/SKILL.md +2 -0
- package/plugins/lisa-openclaw/.codex-plugin/skills/lisa-setup-openclaw/SKILL.md +2 -0
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/skills/phaser-accessibility/SKILL.md +2 -0
- package/plugins/lisa-phaser/.codex-plugin/skills/phaser-asset-pipeline/SKILL.md +2 -0
- package/plugins/lisa-phaser/.codex-plugin/skills/phaser-asset-sourcing/SKILL.md +2 -0
- package/plugins/lisa-phaser/.codex-plugin/skills/phaser-build-deploy/SKILL.md +2 -0
- package/plugins/lisa-phaser/.codex-plugin/skills/phaser-i18n/SKILL.md +2 -0
- package/plugins/lisa-phaser/.codex-plugin/skills/phaser-project-structure/SKILL.md +2 -0
- package/plugins/lisa-phaser/.codex-plugin/skills/phaser-services/SKILL.md +2 -0
- package/plugins/lisa-phaser/.codex-plugin/skills/phaser-testing/SKILL.md +2 -0
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/skills/action-controller-best-practices/SKILL.md +2 -0
- package/plugins/lisa-rails/.codex-plugin/skills/action-view-best-practices/SKILL.md +2 -0
- package/plugins/lisa-rails/.codex-plugin/skills/active-record-model-best-practices/SKILL.md +2 -0
- package/plugins/lisa-rails/.codex-plugin/skills/e2e-coverage-gaps/SKILL.md +2 -0
- package/plugins/lisa-rails/.codex-plugin/skills/fix-linter-error/SKILL.md +2 -0
- package/plugins/lisa-rails/.codex-plugin/skills/improve-code-complexity/SKILL.md +2 -0
- package/plugins/lisa-rails/.codex-plugin/skills/improve-max-lines/SKILL.md +2 -0
- package/plugins/lisa-rails/.codex-plugin/skills/improve-max-lines-per-function/SKILL.md +2 -0
- package/plugins/lisa-rails/.codex-plugin/skills/improve-test-coverage/SKILL.md +2 -0
- package/plugins/lisa-rails/.codex-plugin/skills/jira-add-journey/SKILL.md +2 -0
- package/plugins/lisa-rails/.codex-plugin/skills/jira-create/SKILL.md +2 -0
- package/plugins/lisa-rails/.codex-plugin/skills/jira-evidence/SKILL.md +2 -0
- package/plugins/lisa-rails/.codex-plugin/skills/jira-journey/SKILL.md +2 -0
- package/plugins/lisa-rails/.codex-plugin/skills/jira-verify/SKILL.md +2 -0
- package/plugins/lisa-rails/.codex-plugin/skills/lisa-e2e-coverage-gaps/SKILL.md +2 -0
- package/plugins/lisa-rails/.codex-plugin/skills/ops-check-logs/SKILL.md +2 -0
- package/plugins/lisa-rails/.codex-plugin/skills/ops-deploy/SKILL.md +2 -0
- package/plugins/lisa-rails/.codex-plugin/skills/ops-run-local/SKILL.md +2 -0
- package/plugins/lisa-rails/.codex-plugin/skills/ops-verify-jobs/SKILL.md +2 -0
- package/plugins/lisa-rails/.codex-plugin/skills/ops-verify-telemetry/SKILL.md +2 -0
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-add-ingest/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-add-role/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-ingest/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-lint/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-migrate/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-onboard-me/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-query/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-setup/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-setup-wiki/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-status/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-wiki-add-ingest/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-wiki-add-role/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-wiki-connector-confluence/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-wiki-connector-docs/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-wiki-connector-git/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-wiki-connector-jira/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-wiki-connector-memory/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-wiki-connector-notion/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-wiki-connector-roles/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-wiki-connector-slack/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-wiki-connector-web/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-wiki-doctor/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-wiki-ingest/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-wiki-lint/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-wiki-migrate/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-wiki-onboard-me/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-wiki-query/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-wiki-setup/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-wiki-setup-automations/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-wiki-status/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-wiki-tear-down-automations/SKILL.md +2 -0
- package/plugins/lisa-wiki/.codex-plugin/skills/lisa-wiki-usage/SKILL.md +2 -0
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/hooks/parity-safety-net.sh +769 -37
- package/plugins/src/base/rules/reference/design-source-of-truth.md +1 -1
- package/plugins/src/base/rules/reference/rejection-detection.md +1 -1
- package/scripts/generate-codex-plugin-artifacts.mjs +11 -6
|
@@ -515,6 +515,8 @@ readonly FOLLOW_MAX_BYTES=262144
|
|
|
515
515
|
readonly FOLLOW_MAX_TOTAL_BYTES=524288
|
|
516
516
|
readonly FOLLOW_MAX_FILES=8
|
|
517
517
|
readonly FOLLOW_MAX_DEPTH=3
|
|
518
|
+
readonly FOLLOW_MAX_FUNCTION_DEPTH=16
|
|
519
|
+
readonly FOLLOW_MAX_FUNCTION_CALLS=128
|
|
518
520
|
|
|
519
521
|
# Effective working directory established by a literal `cd` in the command, and
|
|
520
522
|
# the flag that says a `cd` happened whose target could NOT be read literally.
|
|
@@ -526,6 +528,8 @@ follow_paths=$'\n'
|
|
|
526
528
|
follow_names=""
|
|
527
529
|
follow_count=0
|
|
528
530
|
follow_bytes=0
|
|
531
|
+
follow_function_calls=0
|
|
532
|
+
follow_function_bytes=0
|
|
529
533
|
followed_text=""
|
|
530
534
|
followed_rm_text=""
|
|
531
535
|
# Generated from the canonical reviewed supervisor, never from a host manifest.
|
|
@@ -727,8 +731,9 @@ follow_may_invoke() {
|
|
|
727
731
|
# `;` or `|` inside a quoted string is not a statement boundary, and splitting
|
|
728
732
|
# there would invent a command position in the middle of a quoted argument and
|
|
729
733
|
# make prose look like an invocation — the #3604 failure mode, arrived at from
|
|
730
|
-
# the other side.
|
|
731
|
-
#
|
|
734
|
+
# the other side. Literal subshell parentheses carry scope events, while quoted
|
|
735
|
+
# parentheses and substitutions stay in their original statement so `eval
|
|
736
|
+
# "$(cat <file>)"` retains its command position and executed-file inspection.
|
|
732
737
|
#
|
|
733
738
|
# awk rather than a bash char loop because this runs on EVERY intercepted Bash
|
|
734
739
|
# command, and it addresses the text by index instead of accumulating it a
|
|
@@ -736,24 +741,229 @@ follow_may_invoke() {
|
|
|
736
741
|
# arrives through the ENVIRONMENT: `awk -v` processes backslash escapes in the
|
|
737
742
|
# value it is given and would rewrite the command before it was ever read.
|
|
738
743
|
follow_split() {
|
|
739
|
-
SN_FOLLOW_TEXT="$1" awk '
|
|
744
|
+
LC_ALL=C SN_FOLLOW_TEXT="$1" awk '
|
|
745
|
+
# Lexical lookahead keeps an asynchronous return from ending its parent
|
|
746
|
+
# scan before the trailing & event. Aggregate work is capped linearly in
|
|
747
|
+
# input size, including overlap from nested scopes; no partial parse passes.
|
|
748
|
+
function lookahead_step() {
|
|
749
|
+
if (++lookahead_work > 8 * n + 1024) { exit 2 }
|
|
750
|
+
}
|
|
751
|
+
function brace_pipe(from, opening, closing, j, c, quote, escape, braces, nesting, tail, tail_cases, tail_header, word, word_end) {
|
|
752
|
+
quote = ""; escape = 0; braces = 1; nesting = 0; brace_redirect = ""; tail_cases = 0; tail_header = 0;
|
|
753
|
+
for (j = from; j <= n; j++) {
|
|
754
|
+
lookahead_step(); c = substr(s, j, 1);
|
|
755
|
+
if (escape) { escape = 0; continue }
|
|
756
|
+
if (c == "\\" && quote != "\047") { escape = 1; continue }
|
|
757
|
+
if (quote != "") { if (c == quote) { quote = "" } continue }
|
|
758
|
+
if (c == "\"" || c == "\047" || c == "`") { quote = c; continue }
|
|
759
|
+
if (braces > 0) {
|
|
760
|
+
# A case-pattern ) is not the closing parenthesis of its shell.
|
|
761
|
+
if (opening == "(" && c ~ /[A-Za-z_]/) {
|
|
762
|
+
word_end = j + 1;
|
|
763
|
+
while (word_end <= n && substr(s, word_end, 1) ~ /[A-Za-z_0-9-]/) { lookahead_step(); word_end++ }
|
|
764
|
+
word = substr(s, j, word_end - j); j = word_end - 1;
|
|
765
|
+
if (word == "case" && (tail_cases == 0 || !tail_patterns[tail_cases])) { tail_header++ }
|
|
766
|
+
if (word == "in" && tail_header > 0) {
|
|
767
|
+
tail_header--; tail_patterns[++tail_cases] = 1; tail_levels[tail_cases] = braces;
|
|
768
|
+
}
|
|
769
|
+
if (word == "esac" && tail_cases > 0) { tail_cases-- }
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
if (tail_cases > 0 && tail_levels[tail_cases] == braces) {
|
|
773
|
+
if (c == ")" && tail_patterns[tail_cases]) { tail_patterns[tail_cases] = 0; continue }
|
|
774
|
+
if (c == ";" && substr(s, j + 1, 1) == ";") { tail_patterns[tail_cases] = 1; j++; continue }
|
|
775
|
+
}
|
|
776
|
+
if (c == opening) { braces++ }
|
|
777
|
+
if (c == closing && --braces == 0) { tail = j + 1 }
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
# Redirections belong to the compound command too. Quoted operator
|
|
781
|
+
# bytes and nested substitutions are operands, not the pipeline edge.
|
|
782
|
+
if (c == "(") { nesting++; continue }
|
|
783
|
+
if (c == ")") { if (nesting == 0) { brace_redirect = substr(s, tail, j - tail); return 0 } nesting--; continue }
|
|
784
|
+
if (nesting > 0) { continue }
|
|
785
|
+
if (c == "|") { brace_redirect = substr(s, tail, j - tail); return substr(s, j + 1, 1) != "|" }
|
|
786
|
+
if (c == ";" || c == "\n") { brace_redirect = substr(s, tail, j - tail); return 0 }
|
|
787
|
+
if (c == "&" && substr(s, j - 1, 1) !~ /[<>]/ && substr(s, j + 1, 1) != ">") { brace_redirect = substr(s, tail, j - tail); return 0 }
|
|
788
|
+
}
|
|
789
|
+
if (tail > 0) { brace_redirect = substr(s, tail) }
|
|
790
|
+
return 0;
|
|
791
|
+
}
|
|
792
|
+
function list_async(from, j, c, quote, escape, nesting, braces) {
|
|
793
|
+
quote = ""; escape = 0; nesting = 0; braces = 0;
|
|
794
|
+
for (j = from; j <= n; j++) {
|
|
795
|
+
lookahead_step();
|
|
796
|
+
c = substr(s, j, 1);
|
|
797
|
+
if (escape) { escape = 0; continue }
|
|
798
|
+
if (c == "\\" && quote != "\047") { escape = 1; continue }
|
|
799
|
+
if (quote != "") { if (c == quote) { quote = "" } continue }
|
|
800
|
+
if (c == "\"" || c == "\047" || c == "`") { quote = c; continue }
|
|
801
|
+
if (c == "(") { nesting++; continue }
|
|
802
|
+
if (c == ")") { if (nesting == 0) { return 0 } nesting--; continue }
|
|
803
|
+
if (c == "{") { braces++; continue }
|
|
804
|
+
if (c == "}") { if (braces > 0) { braces-- } continue }
|
|
805
|
+
if (nesting > 0 || braces > 0) { continue }
|
|
806
|
+
if (c == ";" || c == "\n") { return 0 }
|
|
807
|
+
if (c == "&" && substr(s, j - 1, 1) !~ /[&<>]/ && substr(s, j + 1, 1) !~ /[&>]/) { return 1 }
|
|
808
|
+
}
|
|
809
|
+
return 0;
|
|
810
|
+
}
|
|
740
811
|
BEGIN {
|
|
741
812
|
s = ENVIRON["SN_FOLLOW_TEXT"]; n = length(s);
|
|
742
|
-
q = ""; esc = 0; start = 1; sep = "o";
|
|
813
|
+
q = ""; esc = 0; start = 1; sep = "o"; parens = 0;
|
|
814
|
+
functions = 0; cases = 0; backtick = 0; brace_depth = 0; post_brace = 0;
|
|
815
|
+
printf "L\t%d\n", list_async(1);
|
|
743
816
|
for (i = 1; i <= n; i++) {
|
|
744
817
|
c = substr(s, i, 1);
|
|
745
|
-
if (esc == 1) { esc = 0; continue }
|
|
818
|
+
if (esc == 1) { if (c == "$") { literal_dollars[i] = 1 } esc = 0; continue }
|
|
746
819
|
if (c == "\\" && q != "\047") { esc = 1; continue }
|
|
747
|
-
if (
|
|
820
|
+
if (functions > 0) {
|
|
821
|
+
if (q != "") { if (c == q) { q = "" } continue }
|
|
822
|
+
if (c == "\"" || c == "\047") { q = c; continue }
|
|
823
|
+
if (c == "{") { functions++ }
|
|
824
|
+
if (c == "}" && --functions == 0) {
|
|
825
|
+
printf "N\t%s\t%s\n", sep, function_name;
|
|
826
|
+
body = substr(s, function_start, i - function_start);
|
|
827
|
+
count = split(body, body_lines, "\n");
|
|
828
|
+
for (j = 1; j <= count; j++) { printf "D\t%s\n", body_lines[j] }
|
|
829
|
+
printf "F\t\n"; start = i + 1; sep = "o";
|
|
830
|
+
}
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
quoted_expression = q == "\"" && ((c == "(" && substr(s, i - 1, 1) == "$" && !literal_dollars[i - 1]) || c == "`");
|
|
834
|
+
if (q != "" && !quoted_expression) { if (c == q) { q = "" } continue }
|
|
748
835
|
if (c == "\"" || c == "\047") { q = c; continue }
|
|
836
|
+
if (c == "`") {
|
|
837
|
+
if (backtick) {
|
|
838
|
+
piece = substr(s, start, i - start);
|
|
839
|
+
if (piece ~ /[^[:space:]]/) { printf "%s\t%s\n", sep, piece; sep = "o" }
|
|
840
|
+
printf "]\t\n"; start = backtick_start; q = backtick_quote; backtick = 0;
|
|
841
|
+
} else {
|
|
842
|
+
backtick_start = start; backtick_quote = q; q = ""; backtick = 1;
|
|
843
|
+
printf "[\t\nL\t%d\n", list_async(i + 1); start = i + 1;
|
|
844
|
+
}
|
|
845
|
+
continue;
|
|
846
|
+
}
|
|
847
|
+
if (i == start && c ~ /[[:space:]]/) { start = i + 1; continue }
|
|
848
|
+
if (c ~ /[[:space:]]/ && substr(s, i - 1, 1) !~ /[[:space:]]/ && substr(s, start, 4) == "case") {
|
|
849
|
+
piece = substr(s, start, i - start);
|
|
850
|
+
if (piece ~ /^case[[:space:]].*[[:space:]]in$/) {
|
|
851
|
+
case_scopes[++cases] = parens;
|
|
852
|
+
case_patterns[cases] = 1; printf "C\t\n"; start = i + 1; continue;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
if (cases > 0 && c ~ /[[:space:];&|)]/ && substr(s, start, 4) == "esac") {
|
|
856
|
+
piece = substr(s, start, i - start);
|
|
857
|
+
if (piece ~ /^esac[[:space:]]*$/) { cases--; printf "K\t\n" }
|
|
858
|
+
}
|
|
859
|
+
if (cases > 0 && case_scopes[cases] == parens && case_patterns[cases]) {
|
|
860
|
+
if (c == ")") { case_patterns[cases] = 0; start = i + 1 }
|
|
861
|
+
continue;
|
|
862
|
+
}
|
|
863
|
+
if (c == "{") {
|
|
864
|
+
piece = substr(s, start, i - start);
|
|
865
|
+
definition = piece ~ /^[[:space:]]*([A-Za-z_][A-Za-z_0-9]*[[:space:]]*\([[:space:]]*\)|function[[:space:]]+[A-Za-z_][A-Za-z_0-9]*([[:space:]]*\([[:space:]]*\))?)[[:space:]]*$/;
|
|
866
|
+
if (definition) {
|
|
867
|
+
function_name = piece;
|
|
868
|
+
sub(/^[[:space:]]*(function[[:space:]]+)?/, "", function_name);
|
|
869
|
+
sub(/[[:space:]\(].*$/, "", function_name);
|
|
870
|
+
functions = 1; function_start = i + 1;
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
previous = i > 1 ? substr(s, i - 1, 1) : "";
|
|
874
|
+
group = (i == 1 || previous ~ /[[:space:];&|()]/) && substr(s, i + 1, 1) ~ /[[:space:];]/ &&
|
|
875
|
+
piece ~ /^[[:space:]]*((then|do|else|!|time)[[:space:]]+)*$/;
|
|
876
|
+
brace_groups[++brace_depth] = group;
|
|
877
|
+
if (group) {
|
|
878
|
+
brace_sep = sep;
|
|
879
|
+
if (piece ~ /[^[:space:]]/) { printf "%s\t%s\n", sep, piece; sep = "o" }
|
|
880
|
+
# A pipeline owns the whole compound command, not just the final
|
|
881
|
+
# statement before |. Snapshot before any brace-body cd/return.
|
|
882
|
+
outgoing = brace_pipe(i + 1, "{", "}");
|
|
883
|
+
brace_pipes[brace_depth] = brace_sep == "|" || outgoing;
|
|
884
|
+
if (brace_pipes[brace_depth]) { printf "(\t%s%s\nL\t%d\n", (brace_sep == "|" ? "|" : ""), (outgoing ? "+" : ""), list_async(i + 1) }
|
|
885
|
+
printf "G\t%s\nL\t%d\n", brace_sep, list_async(i + 1);
|
|
886
|
+
if (brace_redirect ~ /[^[:space:]]/) { printf "I\t%s\n", brace_redirect }
|
|
887
|
+
# P retains incoming stdin provenance without wrapping each body
|
|
888
|
+
# statement in another child: all statements share the brace cwd.
|
|
889
|
+
if (brace_sep == "|") { sep = "P" }
|
|
890
|
+
start = i + 1; continue;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
if (c == "}" && brace_depth > 0) {
|
|
894
|
+
group = brace_groups[brace_depth--];
|
|
895
|
+
if (group) {
|
|
896
|
+
piece = substr(s, start, i - start);
|
|
897
|
+
if (piece ~ /[^[:space:]]/) { printf "%s\t%s\n", sep, piece; sep = "o" }
|
|
898
|
+
printf "H\t\n";
|
|
899
|
+
if (brace_pipes[brace_depth + 1]) { printf ")\t\n" }
|
|
900
|
+
start = i + 1; post_brace = 1; continue;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
if (c == "(") {
|
|
904
|
+
previous = i > 1 ? substr(s, i - 1, 1) : "";
|
|
905
|
+
piece = substr(s, start, i - start);
|
|
906
|
+
header = piece ~ /^[[:space:]]*(function[[:space:]]+)?[A-Za-z_][A-Za-z_0-9]*[[:space:]]*$/ && substr(s, i + 1) ~ /^[[:space:]]*\)/;
|
|
907
|
+
expression = previous ~ /[$<>]/ && !literal_dollars[i - 1] && substr(s, i + 1, 1) != "(";
|
|
908
|
+
group = (parens == 0 || scopes[parens] != 0) &&
|
|
909
|
+
(i == 1 || previous ~ /[[:space:];&|()]/) && substr(s, i + 1, 1) != "(" && !header;
|
|
910
|
+
scopes[++parens] = expression ? 2 : group;
|
|
911
|
+
if (expression) {
|
|
912
|
+
expression_starts[parens] = start;
|
|
913
|
+
expression_quotes[parens] = q; q = "";
|
|
914
|
+
printf "[\t\nL\t%d\n", list_async(i + 1); start = i + 1; continue;
|
|
915
|
+
}
|
|
916
|
+
if (group) {
|
|
917
|
+
piece = substr(s, start, i - start);
|
|
918
|
+
if (piece ~ /[^[:space:]]/) { printf "%s\t%s\n", sep, piece; sep = "o" }
|
|
919
|
+
outgoing = brace_pipe(i + 1, "(", ")");
|
|
920
|
+
printf "(\t%s%s\nL\t%d\n", sep, (outgoing ? "+" : ""), list_async(i + 1);
|
|
921
|
+
if (brace_redirect ~ /[^[:space:]]/) { printf "I\t%s\n", brace_redirect }
|
|
922
|
+
if (sep == "|") { sep = "P" }
|
|
923
|
+
start = i + 1; continue;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
if (c == ")" && parens > 0) {
|
|
927
|
+
piece = substr(s, start, i - start);
|
|
928
|
+
group = scopes[parens--];
|
|
929
|
+
if (group == 2) {
|
|
930
|
+
if (piece ~ /[^[:space:]]/) { printf "%s\t%s\n", sep, piece; sep = "o" }
|
|
931
|
+
printf "]\t\n"; start = expression_starts[parens + 1]; q = expression_quotes[parens + 1]; continue;
|
|
932
|
+
}
|
|
933
|
+
if (group) {
|
|
934
|
+
piece = substr(s, start, i - start);
|
|
935
|
+
if (piece ~ /[^[:space:]]/) { printf "%s\t%s\n", sep, piece; sep = "o" }
|
|
936
|
+
printf ")\t\n"; start = i + 1; post_brace = 1; continue;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
749
939
|
if (c == ";" || c == "&" || c == "|" || c == "\n") {
|
|
750
|
-
|
|
940
|
+
if (parens > 0 && scopes[parens] == 0) { continue }
|
|
941
|
+
if (c == "&" && (substr(s, i - 1, 1) ~ /[<>]/ || substr(s, i + 1, 1) == ">")) { continue }
|
|
942
|
+
piece = substr(s, start, i - start);
|
|
943
|
+
pipe = c == "|" && substr(s, i + 1, 1) != "|";
|
|
944
|
+
if (pipe || sep == "|") { printf "(\t%s%s\nL\t%d\n", (sep == "|" ? "|" : ""), (pipe ? "+" : ""), list_async(start) }
|
|
945
|
+
if (piece ~ /[^[:space:]]/) { printf "%s\t%s\n", post_brace ? "R" : sep, piece }
|
|
946
|
+
post_brace = 0;
|
|
947
|
+
if (pipe || sep == "|") { printf ")\t\n" }
|
|
948
|
+
if (c == ";" && substr(s, i + 1, 1) == ";" && cases > 0) {
|
|
949
|
+
case_patterns[cases] = 1; printf "A\t\n"; i++;
|
|
950
|
+
}
|
|
951
|
+
if (c == "|" && substr(s, i + 1, 1) == "|") {
|
|
952
|
+
i++; sep = "?";
|
|
953
|
+
} else if (c == "&" && substr(s, i + 1, 1) == "&") {
|
|
954
|
+
i++; sep = "&";
|
|
955
|
+
} else if (c == "|") { sep = "|" } else { sep = "o" }
|
|
956
|
+
if (c == "&" && sep == "o" && substr(s, i - 1, 1) !~ /[&<>]/ && substr(s, i + 1, 1) != ">") {
|
|
957
|
+
printf "B\t\n";
|
|
958
|
+
}
|
|
959
|
+
if ((c == ";" || c == "\n" || c == "&") && sep == "o") { printf "L\t%d\n", list_async(i + 1) }
|
|
751
960
|
start = i + 1;
|
|
752
|
-
if (c == "|") { sep = "|" } else { sep = "o" }
|
|
753
961
|
continue
|
|
754
962
|
}
|
|
755
963
|
}
|
|
964
|
+
if (sep == "|") { printf "(\t|\nL\t%d\n", list_async(start) }
|
|
756
965
|
printf "%s\t%s\n", sep, substr(s, start, n - start + 1);
|
|
966
|
+
if (sep == "|") { printf ")\t\n" }
|
|
757
967
|
}
|
|
758
968
|
'
|
|
759
969
|
}
|
|
@@ -818,7 +1028,12 @@ follow_cd() {
|
|
|
818
1028
|
esac
|
|
819
1029
|
case "$d" in
|
|
820
1030
|
/*) ;;
|
|
821
|
-
*)
|
|
1031
|
+
*)
|
|
1032
|
+
# A relative cd cannot recover an unknown base. Falling back to this
|
|
1033
|
+
# hook's PWD would confidently inspect another directory (#4299).
|
|
1034
|
+
[ "$follow_cwd_unknown" -eq 0 ] || return 0
|
|
1035
|
+
d="${follow_cwd:-$PWD}/$d"
|
|
1036
|
+
;;
|
|
822
1037
|
esac
|
|
823
1038
|
follow_normalize "$d"
|
|
824
1039
|
d="$follow_normalized"
|
|
@@ -902,8 +1117,14 @@ follow_locate() {
|
|
|
902
1117
|
# is never executed, so scanning it can only manufacture a false refusal.
|
|
903
1118
|
follow_take() {
|
|
904
1119
|
local path="$1" depth="$2" size="" content="" rm_content="${3-}"
|
|
1120
|
+
local invocation_kind=child
|
|
1121
|
+
if [ "$#" -eq 3 ]; then invocation_kind="$3"; fi
|
|
905
1122
|
case "$follow_paths" in
|
|
906
|
-
*$'\n'"$path"$'\n'*)
|
|
1123
|
+
*$'\n'"$path"$'\n'*)
|
|
1124
|
+
# Sourcing executes in the caller each time; a read cache cannot erase
|
|
1125
|
+
# its directory changes or newly defined functions.
|
|
1126
|
+
[ "$invocation_kind" = source ] || return 0
|
|
1127
|
+
;;
|
|
907
1128
|
esac
|
|
908
1129
|
follow_paths="$follow_paths$path"$'\n'
|
|
909
1130
|
follow_count=$((follow_count + 1))
|
|
@@ -931,22 +1152,31 @@ follow_take() {
|
|
|
931
1152
|
fi
|
|
932
1153
|
follow_names="$follow_names $path"
|
|
933
1154
|
followed_text="$followed_text"$'\n'"$content"
|
|
934
|
-
if [ "$#" -
|
|
1155
|
+
if [ "$#" -ne 4 ]; then
|
|
935
1156
|
rm_content="$content"
|
|
936
1157
|
else
|
|
937
1158
|
rm_content="$(printf '%s' "$rm_content" | sed 's/^[[:space:]]*#.*$//')" || follow_refuse "$path" "supervisor cleanup view could not be read"
|
|
938
1159
|
fi
|
|
939
1160
|
followed_rm_text="$followed_rm_text"$'\n'"$rm_content"
|
|
940
1161
|
if [ "$depth" -lt "$FOLLOW_MAX_DEPTH" ]; then
|
|
941
|
-
#
|
|
942
|
-
#
|
|
943
|
-
# child inherits this directory but cannot change the parent's. Saving and
|
|
944
|
-
# restoring is what keeps a nested `cd` from silently re-pointing the rest
|
|
945
|
-
# of the outer walk.
|
|
1162
|
+
# Child scripts inherit cwd but cannot change their parent. A sourced
|
|
1163
|
+
# script runs in the caller and keeps both cwd and function definitions.
|
|
946
1164
|
local saved_cwd="$follow_cwd" saved_unknown="$follow_cwd_unknown"
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1165
|
+
if [ "$#" -eq 4 ]; then
|
|
1166
|
+
# Only follow_supervisor reaches this form, after exact byte identity and
|
|
1167
|
+
# the public argv profile were authenticated. Its owned bootstrap is
|
|
1168
|
+
# already scanned in full by every text policy; its user payload is
|
|
1169
|
+
# followed separately by follow_supervisor. Re-expanding all bootstrap
|
|
1170
|
+
# functions would re-interpret its internal protocol, not inspect a new
|
|
1171
|
+
# user-controlled file, and multiplied its native hook time fourfold.
|
|
1172
|
+
follow_scan "$content" "$((depth + 1))" 0 1
|
|
1173
|
+
else
|
|
1174
|
+
follow_scan "$content" "$((depth + 1))" 0 0 "$invocation_kind"
|
|
1175
|
+
fi
|
|
1176
|
+
if [ "$invocation_kind" != source ]; then
|
|
1177
|
+
follow_cwd="$saved_cwd"
|
|
1178
|
+
follow_cwd_unknown="$saved_unknown"
|
|
1179
|
+
fi
|
|
950
1180
|
fi
|
|
951
1181
|
}
|
|
952
1182
|
|
|
@@ -954,7 +1184,14 @@ follow_take() {
|
|
|
954
1184
|
# nested inside an already-followed script it is the documented residual above.
|
|
955
1185
|
follow_target() {
|
|
956
1186
|
local token="$1" depth="$2" path=""
|
|
1187
|
+
local invocation_kind="${3:-child}"
|
|
957
1188
|
[ -n "$token" ] || return 0
|
|
1189
|
+
if [ "$follow_cwd_unknown" -ne 0 ]; then
|
|
1190
|
+
case "$token" in
|
|
1191
|
+
/* | '~/'*) ;;
|
|
1192
|
+
*) follow_refuse "$token" "the execution directory is unknown" ;;
|
|
1193
|
+
esac
|
|
1194
|
+
fi
|
|
958
1195
|
# Entry authority belongs to each invocation, not the file-read cache. Only
|
|
959
1196
|
# follow_supervisor may admit the exact public interpreter/argv profile.
|
|
960
1197
|
case "$token" in
|
|
@@ -967,7 +1204,7 @@ follow_target() {
|
|
|
967
1204
|
return 0
|
|
968
1205
|
fi
|
|
969
1206
|
path="$follow_located"
|
|
970
|
-
follow_take "$path" "$depth"
|
|
1207
|
+
follow_take "$path" "$depth" "$invocation_kind"
|
|
971
1208
|
}
|
|
972
1209
|
|
|
973
1210
|
# A command word that IS the executable (`./deploy.sh`, `/tmp/x.sh`). Followed
|
|
@@ -1024,23 +1261,422 @@ follow_next_operand() {
|
|
|
1024
1261
|
follow_operand_index="$j"
|
|
1025
1262
|
}
|
|
1026
1263
|
|
|
1264
|
+
# Resolve a piped file in the PRODUCER's directory, which can differ from the
|
|
1265
|
+
# interpreter's after a subshell closes. Preserve unknown-state provenance too.
|
|
1266
|
+
follow_pipe_target() {
|
|
1267
|
+
local saved_cwd="$follow_cwd" saved_unknown="$follow_cwd_unknown"
|
|
1268
|
+
follow_cwd="$3"
|
|
1269
|
+
follow_cwd_unknown="$4"
|
|
1270
|
+
follow_target "$1" "$2"
|
|
1271
|
+
follow_cwd="$saved_cwd"
|
|
1272
|
+
follow_cwd_unknown="$saved_unknown"
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
# Carry branch uncertainty into sourced declarations as well as cwd changes.
|
|
1276
|
+
# The local projection lasts only for this source invocation, not later scans.
|
|
1277
|
+
follow_source_target() {
|
|
1278
|
+
local follow_source_flow_unknown="$3"
|
|
1279
|
+
follow_target "$1" "$2" source
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
# Extract the literal -c operand without evaluating it or treating positional
|
|
1283
|
+
# arguments after its closing quote as commands. Prefix indexing matches the
|
|
1284
|
+
# whitespace tokens used by follow_scan; the operand keeps its internal bytes.
|
|
1285
|
+
follow_inline() {
|
|
1286
|
+
local stmt="$1" from="$2" depth="$3" body
|
|
1287
|
+
local follow_inline_depth="${follow_inline_depth:-0}"
|
|
1288
|
+
[ "$follow_inline_depth" -lt "$FOLLOW_MAX_FUNCTION_DEPTH" ] || follow_refuse "-c" "inline shells exceed the inspection depth"
|
|
1289
|
+
follow_inline_depth=$((follow_inline_depth + 1))
|
|
1290
|
+
body="$(LC_ALL=C SN_INLINE_TEXT="$stmt" SN_INLINE_FROM="$from" awk '
|
|
1291
|
+
BEGIN {
|
|
1292
|
+
s = ENVIRON["SN_INLINE_TEXT"]; n = length(s); i = 1;
|
|
1293
|
+
for (word = 0; word < ENVIRON["SN_INLINE_FROM"]; word++) {
|
|
1294
|
+
while (i <= n && substr(s, i, 1) ~ /[[:space:]]/) { i++ }
|
|
1295
|
+
while (i <= n && substr(s, i, 1) !~ /[[:space:]]/) { i++ }
|
|
1296
|
+
}
|
|
1297
|
+
while (i <= n && substr(s, i, 1) ~ /[[:space:]]/) { i++ }
|
|
1298
|
+
q = substr(s, i, 1);
|
|
1299
|
+
if (q == "\047" || q == "\"") {
|
|
1300
|
+
start = ++i; esc = 0;
|
|
1301
|
+
for (; i <= n; i++) {
|
|
1302
|
+
c = substr(s, i, 1);
|
|
1303
|
+
if (esc) { esc = 0; continue }
|
|
1304
|
+
if (c == "\\" && q == "\"") { esc = 1; continue }
|
|
1305
|
+
if (c == q) {
|
|
1306
|
+
# Adjacent quoted/unquoted fragments are the same shell operand.
|
|
1307
|
+
# Refuse unsupported concatenation instead of scanning a prefix.
|
|
1308
|
+
if (i < n && substr(s, i + 1, 1) !~ /[[:space:]]/) { exit 1 }
|
|
1309
|
+
printf "%s", substr(s, start, i - start); exit;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
exit 1;
|
|
1313
|
+
}
|
|
1314
|
+
start = i;
|
|
1315
|
+
while (i <= n && substr(s, i, 1) !~ /[[:space:]]/) {
|
|
1316
|
+
c = substr(s, i, 1);
|
|
1317
|
+
if (c == "\047" || c == "\"" || c == "\\") { exit 1 }
|
|
1318
|
+
i++;
|
|
1319
|
+
}
|
|
1320
|
+
printf "%s", substr(s, start, i - start);
|
|
1321
|
+
}
|
|
1322
|
+
')" || follow_refuse "-c" "its command string could not be determined"
|
|
1323
|
+
local saved_cwd="$follow_cwd" saved_unknown="$follow_cwd_unknown"
|
|
1324
|
+
follow_scan "$body" "$depth"
|
|
1325
|
+
follow_cwd="$saved_cwd"
|
|
1326
|
+
follow_cwd_unknown="$saved_unknown"
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
# Resolve literal descriptor duplication without running the proposed shell.
|
|
1330
|
+
# Compound input is established before its body; output is projected when the
|
|
1331
|
+
# producer reaches the pipe. Unknown descriptors refuse rather than drop bytes.
|
|
1332
|
+
follow_redirect_io() {
|
|
1333
|
+
local statement="$1" mode="$2" result
|
|
1334
|
+
result="$(SN_COMPOUND_REDIRECT="$statement" SN_DESCRIPTOR_MAP="$follow_descriptor_map" SN_REDIRECT_CWD="$follow_cwd" SN_REDIRECT_UNKNOWN="$follow_cwd_unknown" python3 - "$mode" <<'PY'
|
|
1335
|
+
import json
|
|
1336
|
+
import os
|
|
1337
|
+
import sys
|
|
1338
|
+
try:
|
|
1339
|
+
mode = sys.argv[1]
|
|
1340
|
+
text = os.environ['SN_COMPOUND_REDIRECT']
|
|
1341
|
+
tokens = []
|
|
1342
|
+
cursor = 0
|
|
1343
|
+
# Keep syntax provenance and byte adjacency: quoted/escaped > is data,
|
|
1344
|
+
# and `cat 2 >&2` names a file rather than redirecting descriptor 2.
|
|
1345
|
+
while cursor < len(text):
|
|
1346
|
+
if text[cursor].isspace():
|
|
1347
|
+
cursor += 1
|
|
1348
|
+
continue
|
|
1349
|
+
start = cursor
|
|
1350
|
+
syntax = text[cursor] in '<>&'
|
|
1351
|
+
plain = True
|
|
1352
|
+
value = ''
|
|
1353
|
+
quote = ''
|
|
1354
|
+
if syntax:
|
|
1355
|
+
while cursor < len(text) and text[cursor] in '<>&':
|
|
1356
|
+
value += text[cursor]; cursor += 1
|
|
1357
|
+
else:
|
|
1358
|
+
while cursor < len(text):
|
|
1359
|
+
char = text[cursor]
|
|
1360
|
+
if not quote and (char.isspace() or char in '<>&'):
|
|
1361
|
+
break
|
|
1362
|
+
if char == '\\' and quote != "'":
|
|
1363
|
+
plain = False
|
|
1364
|
+
cursor += 1
|
|
1365
|
+
if cursor == len(text):
|
|
1366
|
+
raise ValueError('trailing escape')
|
|
1367
|
+
if text[cursor] == '\n':
|
|
1368
|
+
cursor += 1
|
|
1369
|
+
continue
|
|
1370
|
+
if quote == '"' and text[cursor] not in '$`"\\':
|
|
1371
|
+
value += '\\'
|
|
1372
|
+
value += text[cursor]; cursor += 1
|
|
1373
|
+
continue
|
|
1374
|
+
if char in ('"', "'"):
|
|
1375
|
+
if not quote:
|
|
1376
|
+
quote = char; plain = False; cursor += 1
|
|
1377
|
+
continue
|
|
1378
|
+
if quote == char:
|
|
1379
|
+
quote = ''; cursor += 1
|
|
1380
|
+
continue
|
|
1381
|
+
value += char; cursor += 1
|
|
1382
|
+
if quote:
|
|
1383
|
+
raise ValueError('unterminated quote')
|
|
1384
|
+
tokens.append((value, syntax, plain, start, cursor))
|
|
1385
|
+
descriptors = {int(fd): value for fd, value in json.loads(os.environ['SN_DESCRIPTOR_MAP']).items()}
|
|
1386
|
+
operators = ('<', '>', '>>', '<>', '<&', '>&', '&>', '&>>')
|
|
1387
|
+
index = 0
|
|
1388
|
+
while index < len(tokens):
|
|
1389
|
+
fd = None
|
|
1390
|
+
token, syntax, plain, start, end = tokens[index]
|
|
1391
|
+
if token.isdigit() and plain and index + 1 < len(tokens) and tokens[index + 1][1] and end == tokens[index + 1][3]:
|
|
1392
|
+
fd = int(token); index += 1
|
|
1393
|
+
token, syntax, plain, start, end = tokens[index]
|
|
1394
|
+
if not syntax or token not in operators:
|
|
1395
|
+
if mode == 'cat':
|
|
1396
|
+
index += 1
|
|
1397
|
+
continue
|
|
1398
|
+
sys.exit(2)
|
|
1399
|
+
if index + 1 >= len(tokens):
|
|
1400
|
+
sys.exit(2)
|
|
1401
|
+
operator = token
|
|
1402
|
+
operand, operand_syntax, _, _, _ = tokens[index + 1]
|
|
1403
|
+
if operand_syntax:
|
|
1404
|
+
sys.exit(2)
|
|
1405
|
+
index += 2
|
|
1406
|
+
if fd is None:
|
|
1407
|
+
fd = 0 if operator.startswith('<') else 1
|
|
1408
|
+
if operator in ('<&', '>&'):
|
|
1409
|
+
if operand == '-':
|
|
1410
|
+
descriptors[fd] = {'kind': 'closed'}
|
|
1411
|
+
elif operand.isdigit():
|
|
1412
|
+
descriptors[fd] = descriptors.get(int(operand), {'kind': 'unknown'})
|
|
1413
|
+
else:
|
|
1414
|
+
sys.exit(2)
|
|
1415
|
+
else:
|
|
1416
|
+
descriptors[fd] = {'kind': 'file', 'path': operand, 'cwd': os.environ['SN_REDIRECT_CWD'], 'unknown': int(os.environ['SN_REDIRECT_UNKNOWN'])}
|
|
1417
|
+
if operator.startswith('&>'):
|
|
1418
|
+
descriptors[2] = descriptors[fd]
|
|
1419
|
+
output = descriptors[1]['kind']
|
|
1420
|
+
if output == 'unknown' or descriptors[0]['kind'] == 'unknown':
|
|
1421
|
+
sys.exit(2)
|
|
1422
|
+
print(json.dumps({'input': descriptors[0], 'output': output, 'descriptors': descriptors}))
|
|
1423
|
+
except (ValueError, OSError):
|
|
1424
|
+
sys.exit(2)
|
|
1425
|
+
PY
|
|
1426
|
+
)" || follow_refuse "compound redirection" "its input or pipeline output could not be determined"
|
|
1427
|
+
follow_redirect_input="$(printf '%s' "$result" | jq -er '.input.kind')" || follow_refuse "compound redirection" "invalid input projection"
|
|
1428
|
+
follow_redirect_path="$(printf '%s' "$result" | jq -r '.input.path // ""')" || follow_refuse "compound redirection" "invalid input path"
|
|
1429
|
+
follow_redirect_map="$(printf '%s' "$result" | jq -ce '.descriptors')" || follow_refuse "compound redirection" "invalid descriptor projection"
|
|
1430
|
+
follow_redirect_input_cwd="$(printf '%s' "$result" | jq -r '.input.cwd // ""')" || follow_refuse "compound redirection" "invalid input cwd"
|
|
1431
|
+
follow_redirect_input_unknown="$(printf '%s' "$result" | jq -r '.input.unknown // 1')" || follow_refuse "compound redirection" "invalid input uncertainty"
|
|
1432
|
+
follow_redirect_output="$(printf '%s' "$result" | jq -er '.output')" || follow_refuse "compound redirection" "invalid output projection"
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1027
1435
|
# Walk one text for the files it executes. Runs in the PARENT shell, never in a
|
|
1028
1436
|
# command substitution: follow_refuse() must be able to deny, and an exit inside
|
|
1029
1437
|
# a subshell ends that subshell rather than this hook — the mistake documented
|
|
1030
1438
|
# on the rm segmentation below.
|
|
1031
1439
|
follow_scan() {
|
|
1032
1440
|
local text="$1" depth="$2"
|
|
1441
|
+
local function_call_depth="${3:-0}"
|
|
1442
|
+
local authenticated_bootstrap="${4:-0}"
|
|
1443
|
+
local invocation_kind="${5:-child}"
|
|
1444
|
+
local function_flow_unknown="${6:-0}" function_lookup_allowed statement_conditional function_conditional=0
|
|
1445
|
+
local and_cwd="" and_unknown=1 and_ready=0 join_cwd join_unknown
|
|
1446
|
+
if [ "$invocation_kind" = source ]; then
|
|
1447
|
+
function_flow_unknown="${follow_source_flow_unknown:-0}"
|
|
1448
|
+
fi
|
|
1449
|
+
local return_scope_depth=-1
|
|
1450
|
+
local return_list_depth=-1
|
|
1451
|
+
local return_flow_unknown=0
|
|
1452
|
+
local list_depth=0 list_index
|
|
1033
1453
|
local split_out line sep stmt t tj i j n cmd_pos eval_mode inline arg_exec
|
|
1034
1454
|
local heredoc redirected pipe_cat prev_cat pending_operand stdin_mode
|
|
1455
|
+
local pipe_cwd pipe_unknown prev_cwd prev_unknown
|
|
1456
|
+
local follow_stdin_cat="${follow_stdin_cat:-}" follow_stdin_cwd="${follow_stdin_cwd:-}" follow_stdin_unknown="${follow_stdin_unknown:-1}"
|
|
1457
|
+
local follow_redirect_input follow_redirect_path follow_redirect_output follow_redirect_map follow_redirect_input_cwd follow_redirect_input_unknown
|
|
1458
|
+
local follow_descriptor_map="${follow_descriptor_map:-}"
|
|
1459
|
+
if [ -z "$follow_descriptor_map" ]; then
|
|
1460
|
+
follow_descriptor_map='{"0":{"kind":"input"},"1":{"kind":"pipe"},"2":{"kind":"stderr"}}'
|
|
1461
|
+
fi
|
|
1035
1462
|
local find_mode dispatch_pending
|
|
1463
|
+
local scope_depth=0 case_depth=0 function_name="" function_body="" function_index function_found
|
|
1036
1464
|
local -a toks=()
|
|
1037
|
-
|
|
1465
|
+
local -a cwd_scopes=() unknown_scopes=() cat_scopes=() cat_cwd_scopes=() cat_unknown_scopes=() function_scopes=() flow_scopes=() return_flows=()
|
|
1466
|
+
local -a list_cwds=() list_unknowns=() list_functions=() list_flows=() list_asyncs=() list_scope_depths=() list_return_flows=() brace_flows=()
|
|
1467
|
+
local -a brace_descriptor_maps=() scope_descriptor_maps=()
|
|
1468
|
+
local -a brace_stdin_cats=() brace_stdin_cwds=() brace_stdin_unknowns=() scope_stdin_cats=() scope_stdin_cwds=() scope_stdin_unknowns=()
|
|
1469
|
+
local -a case_cwds=() case_unknowns=() case_changes=()
|
|
1470
|
+
if [ "$function_call_depth" -eq 0 ] && [ "$invocation_kind" != source ]; then
|
|
1471
|
+
# Ordinary child shells do not inherit unexported functions. Sourcing and
|
|
1472
|
+
# function calls share the caller's definitions instead.
|
|
1473
|
+
local function_count=0
|
|
1474
|
+
local -a function_names=() function_bodies=() function_uncertainties=()
|
|
1475
|
+
fi
|
|
1476
|
+
split_out="$(follow_split "$text")" || follow_refuse "shell scope" "scope parsing exceeds the inspection budget"
|
|
1038
1477
|
prev_cat=""
|
|
1039
1478
|
while IFS= read -r line; do
|
|
1040
1479
|
sep="${line%%$'\t'*}"
|
|
1041
1480
|
stmt="${line#*$'\t'}"
|
|
1042
|
-
|
|
1043
|
-
|
|
1481
|
+
statement_conditional=0
|
|
1482
|
+
case "$sep" in '&' | '?') statement_conditional=1 ;; esac
|
|
1483
|
+
# A reached && successor observes the preceding successful command's cwd.
|
|
1484
|
+
# Scope/list events invalidate this projection; it must never carry a
|
|
1485
|
+
# child's or a background list's directory into another shell/list.
|
|
1486
|
+
case "$sep" in
|
|
1487
|
+
L | G | H | B | N | F | C | A | K | '(' | '[' | ')' | ']') and_ready=0 ;;
|
|
1488
|
+
esac
|
|
1489
|
+
if [ "$return_list_depth" -ge 0 ]; then
|
|
1490
|
+
case "$sep" in
|
|
1491
|
+
B) [ "$list_depth" -eq "$return_list_depth" ] || continue ;;
|
|
1492
|
+
'(' | '[' | ')' | ']' | G | H) ;;
|
|
1493
|
+
*) continue ;;
|
|
1494
|
+
esac
|
|
1495
|
+
fi
|
|
1496
|
+
if [ "$return_scope_depth" -ge 0 ]; then
|
|
1497
|
+
# A return in a child scope ends that child, not its enclosing function.
|
|
1498
|
+
# Keep only scope events until the matching close restores the parent.
|
|
1499
|
+
case "$sep" in '(' | '[' | ')' | ']' | G | H) ;; *) continue ;; esac
|
|
1500
|
+
fi
|
|
1501
|
+
case "$sep" in
|
|
1502
|
+
L)
|
|
1503
|
+
list_cwds[list_depth]="$follow_cwd"
|
|
1504
|
+
list_unknowns[list_depth]="$follow_cwd_unknown"
|
|
1505
|
+
list_functions[list_depth]=$function_count
|
|
1506
|
+
list_flows[list_depth]=$function_flow_unknown
|
|
1507
|
+
list_asyncs[list_depth]="$stmt"
|
|
1508
|
+
list_scope_depths[list_depth]=$scope_depth
|
|
1509
|
+
list_return_flows[list_depth]=$return_flow_unknown
|
|
1510
|
+
continue
|
|
1511
|
+
;;
|
|
1512
|
+
G)
|
|
1513
|
+
brace_flows[list_depth]=$function_flow_unknown
|
|
1514
|
+
brace_descriptor_maps[list_depth]="$follow_descriptor_map"
|
|
1515
|
+
brace_stdin_cats[list_depth]="$follow_stdin_cat"
|
|
1516
|
+
brace_stdin_cwds[list_depth]="$follow_stdin_cwd"
|
|
1517
|
+
brace_stdin_unknowns[list_depth]="$follow_stdin_unknown"
|
|
1518
|
+
if [ "$stmt" = "|" ]; then
|
|
1519
|
+
follow_stdin_cat="$prev_cat"; follow_stdin_cwd="${prev_cwd:-}"; follow_stdin_unknown="${prev_unknown:-1}"
|
|
1520
|
+
fi
|
|
1521
|
+
case "$stmt" in '&' | '?') function_flow_unknown=1 ;; esac
|
|
1522
|
+
list_depth=$((list_depth + 1)); continue
|
|
1523
|
+
;;
|
|
1524
|
+
H)
|
|
1525
|
+
list_depth=$((list_depth - 1))
|
|
1526
|
+
function_flow_unknown="${brace_flows[list_depth]}"
|
|
1527
|
+
follow_descriptor_map="${brace_descriptor_maps[list_depth]}"
|
|
1528
|
+
follow_stdin_cat="${brace_stdin_cats[list_depth]}"
|
|
1529
|
+
follow_stdin_cwd="${brace_stdin_cwds[list_depth]}"
|
|
1530
|
+
follow_stdin_unknown="${brace_stdin_unknowns[list_depth]}"
|
|
1531
|
+
# A conditional return may exit this function/source before commands
|
|
1532
|
+
# after the brace. Unlike a child return, that uncertainty persists.
|
|
1533
|
+
if [ "$return_flow_unknown" -ne 0 ]; then function_flow_unknown=1; fi
|
|
1534
|
+
continue
|
|
1535
|
+
;;
|
|
1536
|
+
B)
|
|
1537
|
+
# A single & runs the whole AND/OR list asynchronously. Its cwd and
|
|
1538
|
+
# definitions cannot escape into the parent's next list.
|
|
1539
|
+
follow_cwd="${list_cwds[list_depth]}"
|
|
1540
|
+
follow_cwd_unknown="${list_unknowns[list_depth]}"
|
|
1541
|
+
function_flow_unknown="${list_flows[list_depth]}"
|
|
1542
|
+
return_flow_unknown="${list_return_flows[list_depth]}"
|
|
1543
|
+
return_list_depth=-1
|
|
1544
|
+
while [ "$function_count" -gt "${list_functions[list_depth]}" ]; do
|
|
1545
|
+
function_count=$((function_count - 1))
|
|
1546
|
+
function_index=$function_count
|
|
1547
|
+
unset 'function_names[function_index]' 'function_bodies[function_index]' 'function_uncertainties[function_index]'
|
|
1548
|
+
done
|
|
1549
|
+
continue
|
|
1550
|
+
;;
|
|
1551
|
+
N)
|
|
1552
|
+
function_name="${stmt#*$'\t'}"; function_body=""
|
|
1553
|
+
function_conditional="$function_flow_unknown"
|
|
1554
|
+
case "${stmt%%$'\t'*}" in '&' | '?') function_conditional=1 ;; esac
|
|
1555
|
+
continue
|
|
1556
|
+
;;
|
|
1557
|
+
D)
|
|
1558
|
+
if [ "$authenticated_bootstrap" -eq 0 ]; then
|
|
1559
|
+
function_body="$function_body$stmt"$'\n'
|
|
1560
|
+
fi
|
|
1561
|
+
continue
|
|
1562
|
+
;;
|
|
1563
|
+
F)
|
|
1564
|
+
[ "$authenticated_bootstrap" -eq 0 ] || continue
|
|
1565
|
+
function_index=$function_count
|
|
1566
|
+
function_names[function_index]="$function_name"
|
|
1567
|
+
function_bodies[function_index]="$function_body"
|
|
1568
|
+
function_uncertainties[function_index]="$function_conditional"
|
|
1569
|
+
function_count=$((function_count + 1))
|
|
1570
|
+
continue
|
|
1571
|
+
;;
|
|
1572
|
+
C)
|
|
1573
|
+
function_flow_unknown=1
|
|
1574
|
+
case_cwds[case_depth]="$follow_cwd"
|
|
1575
|
+
case_unknowns[case_depth]="$follow_cwd_unknown"
|
|
1576
|
+
case_changes[case_depth]=0
|
|
1577
|
+
case_depth=$((case_depth + 1))
|
|
1578
|
+
continue
|
|
1579
|
+
;;
|
|
1580
|
+
A | K)
|
|
1581
|
+
function_index=$((case_depth - 1))
|
|
1582
|
+
if [ "$follow_cwd" != "${case_cwds[function_index]}" ] || [ "$follow_cwd_unknown" != "${case_unknowns[function_index]}" ]; then
|
|
1583
|
+
case_changes[function_index]=1
|
|
1584
|
+
fi
|
|
1585
|
+
follow_cwd="${case_cwds[function_index]}"
|
|
1586
|
+
follow_cwd_unknown="${case_unknowns[function_index]}"
|
|
1587
|
+
if [ "$sep" = K ]; then
|
|
1588
|
+
if [ "${case_changes[function_index]}" -ne 0 ]; then
|
|
1589
|
+
follow_cwd=""; follow_cwd_unknown=1
|
|
1590
|
+
fi
|
|
1591
|
+
case_depth=$((case_depth - 1))
|
|
1592
|
+
fi
|
|
1593
|
+
continue
|
|
1594
|
+
;;
|
|
1595
|
+
'(' | '[')
|
|
1596
|
+
cwd_scopes[scope_depth]="$follow_cwd"
|
|
1597
|
+
unknown_scopes[scope_depth]="$follow_cwd_unknown"
|
|
1598
|
+
cat_scopes[scope_depth]="$prev_cat"
|
|
1599
|
+
cat_cwd_scopes[scope_depth]="${prev_cwd:-}"
|
|
1600
|
+
cat_unknown_scopes[scope_depth]="${prev_unknown:-1}"
|
|
1601
|
+
scope_descriptor_maps[scope_depth]="$follow_descriptor_map"
|
|
1602
|
+
function_scopes[scope_depth]=$function_count
|
|
1603
|
+
flow_scopes[scope_depth]=$function_flow_unknown
|
|
1604
|
+
return_flows[scope_depth]=$return_flow_unknown
|
|
1605
|
+
scope_stdin_cats[scope_depth]="$follow_stdin_cat"
|
|
1606
|
+
scope_stdin_cwds[scope_depth]="$follow_stdin_cwd"
|
|
1607
|
+
scope_stdin_unknowns[scope_depth]="$follow_stdin_unknown"
|
|
1608
|
+
if [ "$stmt" = "|" ] || [ "$stmt" = "|+" ]; then
|
|
1609
|
+
follow_stdin_cat="$prev_cat"; follow_stdin_cwd="${prev_cwd:-}"; follow_stdin_unknown="${prev_unknown:-1}"
|
|
1610
|
+
follow_descriptor_map="$(printf '%s' "$follow_descriptor_map" | jq -ce '.["0"] = {kind:"input"}')" || follow_refuse "pipeline input" "its descriptor could not be established"
|
|
1611
|
+
fi
|
|
1612
|
+
case "$stmt" in
|
|
1613
|
+
*+)
|
|
1614
|
+
follow_descriptor_map="$(printf '%s' "$follow_descriptor_map" | jq -ce '.["1"] = {kind:"pipe"}')" || follow_refuse "pipeline output" "its descriptor could not be established"
|
|
1615
|
+
;;
|
|
1616
|
+
esac
|
|
1617
|
+
scope_depth=$((scope_depth + 1))
|
|
1618
|
+
list_depth=$((list_depth + 1))
|
|
1619
|
+
continue
|
|
1620
|
+
;;
|
|
1621
|
+
')' | ']')
|
|
1622
|
+
scope_depth=$((scope_depth - 1))
|
|
1623
|
+
list_depth=$((list_depth - 1))
|
|
1624
|
+
follow_cwd="${cwd_scopes[scope_depth]}"
|
|
1625
|
+
follow_descriptor_map="${scope_descriptor_maps[scope_depth]}"
|
|
1626
|
+
follow_cwd_unknown="${unknown_scopes[scope_depth]}"
|
|
1627
|
+
function_flow_unknown="${flow_scopes[scope_depth]}"
|
|
1628
|
+
return_flow_unknown="${return_flows[scope_depth]}"
|
|
1629
|
+
follow_stdin_cat="${scope_stdin_cats[scope_depth]}"
|
|
1630
|
+
follow_stdin_cwd="${scope_stdin_cwds[scope_depth]}"
|
|
1631
|
+
follow_stdin_unknown="${scope_stdin_unknowns[scope_depth]}"
|
|
1632
|
+
while [ "$function_count" -gt "${function_scopes[scope_depth]}" ]; do
|
|
1633
|
+
function_count=$((function_count - 1))
|
|
1634
|
+
function_index=$function_count
|
|
1635
|
+
unset 'function_names[function_index]' 'function_bodies[function_index]' 'function_uncertainties[function_index]'
|
|
1636
|
+
done
|
|
1637
|
+
if [ "$sep" = ']' ]; then
|
|
1638
|
+
prev_cat="${cat_scopes[scope_depth]}"
|
|
1639
|
+
prev_cwd="${cat_cwd_scopes[scope_depth]}"
|
|
1640
|
+
prev_unknown="${cat_unknown_scopes[scope_depth]}"
|
|
1641
|
+
fi
|
|
1642
|
+
if [ "$return_scope_depth" -ge 0 ] && [ "$scope_depth" -lt "$return_scope_depth" ]; then
|
|
1643
|
+
return_scope_depth=-1
|
|
1644
|
+
fi
|
|
1645
|
+
continue
|
|
1646
|
+
;;
|
|
1647
|
+
I)
|
|
1648
|
+
follow_redirect_io "$stmt" compound
|
|
1649
|
+
follow_descriptor_map="$follow_redirect_map"
|
|
1650
|
+
case "$follow_redirect_input" in
|
|
1651
|
+
file)
|
|
1652
|
+
follow_stdin_cat="$follow_redirect_path"
|
|
1653
|
+
follow_stdin_cwd="$follow_redirect_input_cwd"; follow_stdin_unknown="$follow_redirect_input_unknown"
|
|
1654
|
+
;;
|
|
1655
|
+
closed) follow_stdin_cat="" ;;
|
|
1656
|
+
input) ;;
|
|
1657
|
+
*) follow_refuse "compound redirection" "its input source could not be determined" ;;
|
|
1658
|
+
esac
|
|
1659
|
+
continue
|
|
1660
|
+
;;
|
|
1661
|
+
R)
|
|
1662
|
+
# The whole compound tail was applied before its body by I. Its cat
|
|
1663
|
+
# producer already records whether its effective output reaches pipe.
|
|
1664
|
+
continue
|
|
1665
|
+
;;
|
|
1666
|
+
esac
|
|
1667
|
+
join_cwd="$follow_cwd"; join_unknown="$follow_cwd_unknown"
|
|
1668
|
+
if [ "$sep" = '&' ] && [ "$and_ready" -eq 1 ]; then
|
|
1669
|
+
follow_cwd="$and_cwd"; follow_cwd_unknown="$and_unknown"
|
|
1670
|
+
fi
|
|
1671
|
+
and_ready=0
|
|
1672
|
+
pipe_cat="$follow_stdin_cat"
|
|
1673
|
+
pipe_cwd="$follow_stdin_cwd"
|
|
1674
|
+
pipe_unknown="$follow_stdin_unknown"
|
|
1675
|
+
if [ "$sep" = "|" ]; then
|
|
1676
|
+
pipe_cat="$prev_cat"
|
|
1677
|
+
pipe_cwd="${prev_cwd:-}"
|
|
1678
|
+
pipe_unknown="${prev_unknown:-1}"
|
|
1679
|
+
fi
|
|
1044
1680
|
prev_cat=""
|
|
1045
1681
|
case "$stmt" in
|
|
1046
1682
|
*[![:space:]]*) ;;
|
|
@@ -1056,6 +1692,7 @@ follow_scan() {
|
|
|
1056
1692
|
[ "$n" -gt 0 ] || continue
|
|
1057
1693
|
i=0
|
|
1058
1694
|
cmd_pos=1
|
|
1695
|
+
function_lookup_allowed=1
|
|
1059
1696
|
eval_mode=0
|
|
1060
1697
|
arg_exec=0
|
|
1061
1698
|
pending_operand=0
|
|
@@ -1107,7 +1744,65 @@ follow_scan() {
|
|
|
1107
1744
|
follow_unwrap "${toks[i]}"
|
|
1108
1745
|
t="$follow_unwrapped"
|
|
1109
1746
|
fi
|
|
1747
|
+
function_found=0
|
|
1748
|
+
for ((function_index = function_count - 1; function_lookup_allowed && function_index >= 0; function_index--)); do
|
|
1749
|
+
if [ "$t" = "${function_names[function_index]}" ]; then
|
|
1750
|
+
[ "${function_uncertainties[function_index]}" -eq 0 ] || follow_refuse "$t" "its function definition is conditional"
|
|
1751
|
+
# Calls execute in this shell and do not consume a file-follow depth.
|
|
1752
|
+
# A separate bound refuses recursion without truncating the body.
|
|
1753
|
+
[ "$function_call_depth" -lt "$FOLLOW_MAX_FUNCTION_DEPTH" ] || follow_refuse "$t" "function calls exceed the inspection depth"
|
|
1754
|
+
follow_function_calls=$((follow_function_calls + 1))
|
|
1755
|
+
follow_function_bytes=$((follow_function_bytes + ${#function_bodies[function_index]}))
|
|
1756
|
+
if [ "$follow_function_calls" -gt "$FOLLOW_MAX_FUNCTION_CALLS" ] || [ "$follow_function_bytes" -gt "$FOLLOW_MAX_TOTAL_BYTES" ]; then
|
|
1757
|
+
follow_refuse "$t" "function expansion exceeds the inspection budget"
|
|
1758
|
+
fi
|
|
1759
|
+
local call_cwd="$follow_cwd" call_unknown="$follow_cwd_unknown"
|
|
1760
|
+
follow_scan "${function_bodies[function_index]}" "$depth" "$((function_call_depth + 1))" 0 child "$((function_flow_unknown || statement_conditional))"
|
|
1761
|
+
if { [ "$function_flow_unknown" -ne 0 ] || [ "$statement_conditional" -ne 0 ]; } && { [ "$follow_cwd" != "$call_cwd" ] || [ "$follow_cwd_unknown" != "$call_unknown" ]; }; then
|
|
1762
|
+
follow_cwd=""; follow_cwd_unknown=1
|
|
1763
|
+
fi
|
|
1764
|
+
function_found=1
|
|
1765
|
+
break
|
|
1766
|
+
fi
|
|
1767
|
+
done
|
|
1768
|
+
if [ "$function_found" -eq 1 ]; then
|
|
1769
|
+
cmd_pos=0; i=$((i + 1)); continue
|
|
1770
|
+
fi
|
|
1110
1771
|
case "$t" in
|
|
1772
|
+
return)
|
|
1773
|
+
if [ "$statement_conditional" -ne 0 ]; then function_flow_unknown=1; fi
|
|
1774
|
+
if { [ "$function_call_depth" -gt 0 ] || [ "$invocation_kind" = source ]; } && [ "$function_flow_unknown" -ne 0 ]; then
|
|
1775
|
+
return_flow_unknown=1
|
|
1776
|
+
fi
|
|
1777
|
+
if { [ "$function_call_depth" -gt 0 ] || [ "$invocation_kind" = source ]; } && [ "$function_flow_unknown" -eq 0 ]; then
|
|
1778
|
+
# Ordinary braces share the shell but have distinct list frames.
|
|
1779
|
+
# A surrounding background brace list ends only its child; do not
|
|
1780
|
+
# cross a literal subshell boundary while finding that parent list.
|
|
1781
|
+
for ((list_index = list_depth; list_index >= 0; list_index--)); do
|
|
1782
|
+
[ "${list_scope_depths[list_index]}" -eq "$scope_depth" ] || break
|
|
1783
|
+
if [ "${list_asyncs[list_index]}" -ne 0 ]; then
|
|
1784
|
+
return_list_depth="$list_index"
|
|
1785
|
+
break
|
|
1786
|
+
fi
|
|
1787
|
+
done
|
|
1788
|
+
if [ "$return_list_depth" -ge 0 ]; then
|
|
1789
|
+
:
|
|
1790
|
+
elif [ "$scope_depth" -eq 0 ]; then
|
|
1791
|
+
return 0
|
|
1792
|
+
else
|
|
1793
|
+
return_scope_depth="$scope_depth"
|
|
1794
|
+
fi
|
|
1795
|
+
fi
|
|
1796
|
+
cmd_pos=0; i=$((i + 1)); continue
|
|
1797
|
+
;;
|
|
1798
|
+
if | while | until)
|
|
1799
|
+
function_flow_unknown=1
|
|
1800
|
+
i=$((i + 1)); continue
|
|
1801
|
+
;;
|
|
1802
|
+
for | select)
|
|
1803
|
+
function_flow_unknown=1
|
|
1804
|
+
cmd_pos=0; i=$((i + 1)); continue
|
|
1805
|
+
;;
|
|
1111
1806
|
# Prefixes that keep the command position open: assignments, wrappers,
|
|
1112
1807
|
# and the shell keywords a statement can start with. Without them
|
|
1113
1808
|
# `if …; then bash <file>` and `env bash <file>` walk past the
|
|
@@ -1142,12 +1837,17 @@ follow_scan() {
|
|
|
1142
1837
|
# which keeps working when options sit in between — `timeout -s KILL
|
|
1143
1838
|
# 5 bash <file>` reaches `bash` just as `timeout 5 bash <file>` does.
|
|
1144
1839
|
pending_operand=1
|
|
1840
|
+
function_lookup_allowed=0
|
|
1145
1841
|
i=$((i + 1))
|
|
1146
1842
|
continue
|
|
1147
1843
|
;;
|
|
1148
1844
|
sudo | env | command | exec | nohup | nice | time | builtin \
|
|
1149
|
-
| do | then | else |
|
|
1845
|
+
| do | then | else | '!' | '{' \
|
|
1150
1846
|
| */sudo | */env | */nohup | */nice | */time)
|
|
1847
|
+
case "$t" in
|
|
1848
|
+
sudo | env | command | exec | nohup | nice | builtin | */sudo | */env | */nohup | */nice) function_lookup_allowed=0 ;;
|
|
1849
|
+
do | then | else) function_flow_unknown=1 ;;
|
|
1850
|
+
esac
|
|
1151
1851
|
i=$((i + 1))
|
|
1152
1852
|
continue
|
|
1153
1853
|
;;
|
|
@@ -1185,6 +1885,9 @@ follow_scan() {
|
|
|
1185
1885
|
# place would resolve later tokens from a directory the shell left.
|
|
1186
1886
|
follow_cd "${HOME:-}"
|
|
1187
1887
|
fi
|
|
1888
|
+
if [ "$function_flow_unknown" -ne 0 ]; then
|
|
1889
|
+
follow_cwd=""; follow_cwd_unknown=1
|
|
1890
|
+
fi
|
|
1188
1891
|
cmd_pos=0
|
|
1189
1892
|
i=$((i + 1))
|
|
1190
1893
|
continue
|
|
@@ -1194,7 +1897,11 @@ follow_scan() {
|
|
|
1194
1897
|
j="$follow_operand_index"
|
|
1195
1898
|
if [ "$j" -lt "$n" ]; then
|
|
1196
1899
|
follow_unwrap "${toks[j]}"
|
|
1197
|
-
|
|
1900
|
+
local source_cwd="$follow_cwd" source_unknown="$follow_cwd_unknown"
|
|
1901
|
+
follow_source_target "$follow_unwrapped" "$depth" "$((function_flow_unknown || statement_conditional))"
|
|
1902
|
+
if { [ "$function_flow_unknown" -ne 0 ] || [ "$statement_conditional" -ne 0 ]; } && { [ "$follow_cwd" != "$source_cwd" ] || [ "$follow_cwd_unknown" != "$source_unknown" ]; }; then
|
|
1903
|
+
follow_cwd=""; follow_cwd_unknown=1
|
|
1904
|
+
fi
|
|
1198
1905
|
fi
|
|
1199
1906
|
cmd_pos=0
|
|
1200
1907
|
i=$((j + 1))
|
|
@@ -1203,14 +1910,32 @@ follow_scan() {
|
|
|
1203
1910
|
cat | '<')
|
|
1204
1911
|
follow_next_operand "$i" "$n"
|
|
1205
1912
|
j="$follow_operand_index"
|
|
1206
|
-
|
|
1207
|
-
|
|
1913
|
+
follow_unwrapped=""
|
|
1914
|
+
if [ "$j" -lt "$n" ]; then follow_unwrap "${toks[j]}"; fi
|
|
1915
|
+
if [ "$t" = cat ] && { [ "$j" -ge "$n" ] || [ "$follow_unwrapped" = - ]; }; then
|
|
1916
|
+
# Bare cat and cat - forward the group's named stdin unchanged.
|
|
1917
|
+
prev_cat="$pipe_cat"; prev_cwd="$pipe_cwd"; prev_unknown="$pipe_unknown"
|
|
1918
|
+
if [ "$eval_mode" -eq 1 ] && [ -n "$prev_cat" ]; then
|
|
1919
|
+
follow_pipe_target "$prev_cat" "$depth" "$prev_cwd" "$prev_unknown"
|
|
1920
|
+
fi
|
|
1921
|
+
elif [ "$j" -lt "$n" ]; then
|
|
1208
1922
|
if [ "$eval_mode" -eq 1 ]; then
|
|
1209
1923
|
follow_target "$follow_unwrapped" "$depth"
|
|
1210
1924
|
else
|
|
1211
|
-
# Not executed here —
|
|
1212
|
-
#
|
|
1925
|
+
# Not executed here — the pipe consumer decides whether the
|
|
1926
|
+
# producer's file content becomes an executable shell input.
|
|
1213
1927
|
prev_cat="$follow_unwrapped"
|
|
1928
|
+
prev_cwd="$follow_cwd"; prev_unknown="$follow_cwd_unknown"
|
|
1929
|
+
fi
|
|
1930
|
+
fi
|
|
1931
|
+
if [ -n "$prev_cat" ] && [ "$eval_mode" -eq 0 ]; then
|
|
1932
|
+
if [[ "$stmt" == *'>'* ]] || [ "$follow_descriptor_map" != '{"0":{"kind":"input"},"1":{"kind":"pipe"},"2":{"kind":"stderr"}}' ]; then
|
|
1933
|
+
follow_redirect_io "$stmt" cat
|
|
1934
|
+
case "$follow_redirect_output" in
|
|
1935
|
+
pipe) ;;
|
|
1936
|
+
file | closed | stderr | input) prev_cat="" ;;
|
|
1937
|
+
*) follow_refuse "cat redirection" "its output destination could not be determined" ;;
|
|
1938
|
+
esac
|
|
1214
1939
|
fi
|
|
1215
1940
|
fi
|
|
1216
1941
|
cmd_pos=0
|
|
@@ -1295,18 +2020,17 @@ follow_scan() {
|
|
|
1295
2020
|
continue
|
|
1296
2021
|
fi
|
|
1297
2022
|
if [ "$inline" -eq 1 ]; then
|
|
1298
|
-
#
|
|
1299
|
-
#
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
i="$j"
|
|
2023
|
+
# The command string runs in a child shell. It gets its own function
|
|
2024
|
+
# map and cwd effects; positional arguments are data, not commands.
|
|
2025
|
+
follow_inline "$stmt" "$j" "$depth"
|
|
2026
|
+
cmd_pos=0
|
|
2027
|
+
i="$n"
|
|
1304
2028
|
continue
|
|
1305
2029
|
fi
|
|
1306
2030
|
if [ "$stdin_mode" -eq 1 ]; then
|
|
1307
2031
|
# `sh -s -- -y` reads its program from stdin; tokens after `--` are
|
|
1308
2032
|
# positional arguments to that program, never a script path.
|
|
1309
|
-
[ -z "$pipe_cat" ] ||
|
|
2033
|
+
[ -z "$pipe_cat" ] || follow_pipe_target "$pipe_cat" "$depth" "$pipe_cwd" "$pipe_unknown"
|
|
1310
2034
|
cmd_pos=0
|
|
1311
2035
|
i="$n"
|
|
1312
2036
|
continue
|
|
@@ -1320,7 +2044,7 @@ follow_scan() {
|
|
|
1320
2044
|
fi
|
|
1321
2045
|
# No operand: an interactive or stdin-fed interpreter. Only the
|
|
1322
2046
|
# `cat <file> | bash` shape names a FILE, and only that is followed.
|
|
1323
|
-
[ -z "$pipe_cat" ] ||
|
|
2047
|
+
[ -z "$pipe_cat" ] || follow_pipe_target "$pipe_cat" "$depth" "$pipe_cwd" "$pipe_unknown"
|
|
1324
2048
|
cmd_pos=0
|
|
1325
2049
|
i="$j"
|
|
1326
2050
|
continue
|
|
@@ -1354,6 +2078,14 @@ follow_scan() {
|
|
|
1354
2078
|
cmd_pos=0
|
|
1355
2079
|
i=$((i + 1))
|
|
1356
2080
|
done
|
|
2081
|
+
# Keep successful-branch provenance separate from the continuation join.
|
|
2082
|
+
# OR can skip its RHS and still reach a following &&, so it cannot prove
|
|
2083
|
+
# that RHS's cwd. A semicolon observes every possible preceding outcome.
|
|
2084
|
+
and_cwd="$follow_cwd"; and_unknown="$follow_cwd_unknown"
|
|
2085
|
+
if [ "$sep" != '?' ] && [ "$function_flow_unknown" -eq 0 ]; then and_ready=1; fi
|
|
2086
|
+
if [ "$statement_conditional" -ne 0 ] && { [ "$join_unknown" -ne 0 ] || [ "$follow_cwd_unknown" -ne 0 ] || [ "$follow_cwd" != "$join_cwd" ]; }; then
|
|
2087
|
+
follow_cwd=""; follow_cwd_unknown=1
|
|
2088
|
+
fi
|
|
1357
2089
|
done <<<"$split_out"
|
|
1358
2090
|
}
|
|
1359
2091
|
|