@herbertgao/pi-extensions 2026.8.13 → 2026.8.15
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/README.md +19 -14
- package/THIRD_PARTY_NOTICES.md +0 -1
- package/node_modules/@czottmann/pi-automode/CHANGELOG.md +19 -0
- package/node_modules/@czottmann/pi-automode/README.md +5 -1
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +126 -13
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +7 -3
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +2 -0
- package/node_modules/@czottmann/pi-automode/extensions/auto-mode/hard-deny.ts +134 -23
- package/node_modules/@czottmann/pi-automode/package.json +1 -1
- package/node_modules/@herbertgao/{pi-stash → pi-bark}/LICENSE +4 -5
- package/node_modules/@herbertgao/pi-bark/README.md +44 -0
- package/node_modules/@herbertgao/pi-bark/assets/pi-icon.png +0 -0
- package/node_modules/@herbertgao/pi-bark/package.json +53 -0
- package/node_modules/@herbertgao/pi-bark/src/index.ts +178 -0
- package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +2 -1
- package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +8 -1
- package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
- package/node_modules/@narumitw/pi-btw/README.md +125 -88
- package/node_modules/@narumitw/pi-btw/dist/index.ts +140 -45
- package/node_modules/@narumitw/pi-btw/dist/index.ts.map +2 -2
- package/node_modules/@narumitw/pi-btw/package.json +5 -8
- package/node_modules/@narumitw/pi-btw/src/btw.ts +31 -26
- package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +106 -17
- package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -2
- package/node_modules/@narumitw/pi-btw/src/settings.ts +16 -0
- package/node_modules/@tifan/pi-handoff/README.md +36 -13
- package/node_modules/@tifan/pi-handoff/package.json +2 -2
- package/node_modules/@tifan/pi-handoff/src/index.ts +160 -4
- package/node_modules/@tifan/pi-preferred-thinking/README.md +1 -1
- package/node_modules/@tifan/pi-preferred-thinking/package.json +1 -1
- package/node_modules/@tifan/pi-preferred-thinking/src/index.ts +15 -2
- package/node_modules/@tifan/pi-recap/README.md +1 -1
- package/node_modules/@tifan/pi-recap/package.json +2 -2
- package/node_modules/@tifan/pi-recap/src/index.ts +15 -0
- package/node_modules/@tifan/pi-rename/README.md +42 -21
- package/node_modules/@tifan/pi-rename/package.json +1 -1
- package/node_modules/@tifan/pi-rename/src/index.ts +62 -11
- package/node_modules/@tifan/pi-rename/src/language.ts +18 -0
- package/node_modules/@tifan/pi-rename/src/models.ts +69 -13
- package/node_modules/@tifan/pi-rename/src/naming.ts +36 -7
- package/node_modules/@tifan/pi-rename/src/sanitize.ts +22 -14
- package/node_modules/pi-lens/CHANGELOG.md +320 -0
- package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +79 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +16 -1
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +13 -6
- package/node_modules/pi-lens/dist/clients/availability-probe-flight.js +12 -0
- package/node_modules/pi-lens/dist/clients/biome-client.js +7 -0
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +20 -0
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +56 -19
- package/node_modules/pi-lens/dist/clients/build-identity.js +95 -0
- package/node_modules/pi-lens/dist/clients/bus-publish.js +3 -0
- package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +2 -1
- package/node_modules/pi-lens/dist/clients/cache-observability.js +2 -1
- package/node_modules/pi-lens/dist/clients/cascade-logger.js +15 -1
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +14 -11
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +56 -4
- package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +14 -0
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +20 -3
- package/node_modules/pi-lens/dist/clients/deps/minimatch.js +1 -1
- package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +39 -0
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +150 -9
- package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +231 -9
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +205 -100
- package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +87 -0
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +21 -5
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +174 -55
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +5 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +16 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +54 -9
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +1 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +37 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +4 -1
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +11 -4
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +4 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +8 -3
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +76 -25
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +4 -0
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +19 -7
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +7 -13
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +51 -17
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +66 -1
- package/node_modules/pi-lens/dist/clients/file-utils.js +157 -7
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +5 -0
- package/node_modules/pi-lens/dist/clients/formatters.js +28 -3
- package/node_modules/pi-lens/dist/clients/git-guard.js +197 -47
- package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +32 -1
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +5 -1
- package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +7 -0
- package/node_modules/pi-lens/dist/clients/installer/index.js +131 -34
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +3 -1
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +4 -13
- package/node_modules/pi-lens/dist/clients/instance-registry-lock.js +145 -0
- package/node_modules/pi-lens/dist/clients/instance-registry.js +432 -122
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +6 -1
- package/node_modules/pi-lens/dist/clients/language-profile.js +4 -2
- package/node_modules/pi-lens/dist/clients/latency-logger.js +19 -1
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
- package/node_modules/pi-lens/dist/clients/lens-map.js +3 -1
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +4 -0
- package/node_modules/pi-lens/dist/clients/lsp/client.js +516 -31
- package/node_modules/pi-lens/dist/clients/lsp/config.js +25 -2
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +9 -4
- package/node_modules/pi-lens/dist/clients/lsp/index.js +509 -182
- package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +5 -0
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +36 -8
- package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +116 -4
- package/node_modules/pi-lens/dist/clients/lsp/server.js +142 -13
- package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +99 -0
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +16 -6
- package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +32 -23
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +16 -2
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +12 -6
- package/node_modules/pi-lens/dist/clients/mcp/session.js +14 -1
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +23 -1
- package/node_modules/pi-lens/dist/clients/message-end-attribution.js +30 -0
- package/node_modules/pi-lens/dist/clients/metrics-history.js +21 -4
- package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +232 -18
- package/node_modules/pi-lens/dist/clients/package-manager.js +6 -18
- package/node_modules/pi-lens/dist/clients/path-keyed-map.js +14 -1
- package/node_modules/pi-lens/dist/clients/path-utils.js +32 -0
- package/node_modules/pi-lens/dist/clients/pipeline.js +0 -8
- package/node_modules/pi-lens/dist/clients/process-singletons.js +210 -0
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +2 -1
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +10 -4
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +33 -11
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +24 -2
- package/node_modules/pi-lens/dist/clients/read-guard.js +74 -7
- package/node_modules/pi-lens/dist/clients/recent-touches.js +17 -5
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +639 -62
- package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +5 -1
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +21 -1
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +68 -0
- package/node_modules/pi-lens/dist/clients/runtime-session.js +30 -12
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +15 -0
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +106 -7
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +242 -27
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +158 -18
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +48 -17
- package/node_modules/pi-lens/dist/clients/session-lifecycle.js +238 -42
- package/node_modules/pi-lens/dist/clients/sg-runner.js +27 -15
- package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +338 -0
- package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +37 -0
- package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +15 -1
- package/node_modules/pi-lens/dist/clients/startup-scan.js +6 -7
- package/node_modules/pi-lens/dist/clients/startup-timing.js +69 -4
- package/node_modules/pi-lens/dist/clients/string-utils.js +20 -0
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +419 -88
- package/node_modules/pi-lens/dist/clients/tool-policy.js +1 -1
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +11 -5
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +17 -1
- package/node_modules/pi-lens/dist/clients/warm-attach.js +8 -10
- package/node_modules/pi-lens/dist/clients/word-index-logger.js +13 -1
- package/node_modules/pi-lens/dist/clients/word-index-store.js +448 -0
- package/node_modules/pi-lens/dist/clients/word-index.js +582 -104
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +17 -0
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +26 -6
- package/node_modules/pi-lens/dist/index.js +11243 -6651
- package/node_modules/pi-lens/dist/mcp/build-staleness.js +8 -7
- package/node_modules/pi-lens/dist/mcp/cli.js +3 -1
- package/node_modules/pi-lens/dist/mcp/server.js +6 -1
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +4 -0
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +14 -5
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +3 -1
- package/node_modules/pi-lens/docs/agent-guide.md +4 -2
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +41 -10
- package/node_modules/pi-lens/docs/astplayground.md +26 -17
- package/node_modules/pi-lens/package.json +5 -1
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/c-no-malloc-free-test.yml +20 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/cpp-no-malloc-free-test.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/cpp-no-printf-test.yml +19 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-fmt-println-test.yml +19 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-panic-in-lib-test.yml +17 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-no-underscore-func-name-test.yml +18 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-prefer-errors-is-test.yml +15 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/go-prefer-string-builder-test.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/hyphenated-svg-attribute-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-lateinit-test.yml +17 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-nullable-boolean-test.yml +15 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-println-test.yml +19 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-unnecessary-let-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-unsafe-cast-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-no-var-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-prefer-data-class-test.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/kotlin-prefer-is-empty-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-important-test.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-raw-types-test.yml +30 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-string-concat-in-loop-test.yml +100 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-system-out-println-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/prefer-string-is-empty-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/python-optional-type-test.yml +9 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/reducible-list-comprehension-test.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ruby-symbol-to-proc-candidate-test.yml +10 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/rust-no-chars-enumerate-test.yml +7 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/c-no-malloc-free.yml +39 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/cpp-no-malloc-free.yml +45 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/cpp-no-printf.yml +41 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-fmt-println.yml +43 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-panic-in-lib.yml +41 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-no-underscore-func-name.yml +35 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-prefer-errors-is.yml +40 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/go-prefer-string-builder.yml +43 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/hyphenated-svg-attribute.yml +32 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-lateinit.yml +37 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-nullable-boolean.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-println.yml +42 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-unnecessary-let.yml +28 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-unsafe-cast.yml +28 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-no-var.yml +35 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-prefer-data-class.yml +66 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/kotlin-prefer-is-empty.yml +35 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-important.yml +21 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-types.yml +33 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-string-concat-in-loop.yml +90 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-system-out-println.yml +16 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/prefer-string-is-empty.yml +11 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/python-optional-type.yml +22 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/reducible-list-comprehension.yml +29 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/ruby-symbol-to-proc-candidate.yml +25 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/rust-no-chars-enumerate.yml +19 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +35 -0
- package/node_modules/pi-mcp-adapter/README.md +5 -2
- package/node_modules/pi-mcp-adapter/cli.js +4 -4
- package/node_modules/pi-mcp-adapter/commands.ts +1 -1
- package/node_modules/pi-mcp-adapter/config.ts +1 -0
- package/node_modules/pi-mcp-adapter/dist/config.js +1 -0
- package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.d.ts +24 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js +336 -0
- package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js.map +1 -0
- package/node_modules/pi-mcp-adapter/dist/types.d.ts +3 -1
- package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
- package/node_modules/pi-mcp-adapter/index.ts +90 -6
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +124 -32
- package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +0 -2
- package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +89 -0
- package/node_modules/pi-mcp-adapter/mcp-references.ts +9 -1
- package/node_modules/pi-mcp-adapter/package.json +1 -1
- package/node_modules/pi-mcp-adapter/proxy-modes.ts +62 -22
- package/node_modules/pi-mcp-adapter/request-headers-command.ts +1 -1
- package/node_modules/pi-mcp-adapter/server-manager.ts +6 -0
- package/node_modules/pi-mcp-adapter/types.ts +3 -1
- package/node_modules/pi-web-access/CHANGELOG.md +42 -0
- package/node_modules/pi-web-access/README.md +26 -10
- package/node_modules/pi-web-access/curator-page.ts +12 -3
- package/node_modules/pi-web-access/curator-server.ts +3 -1
- package/node_modules/pi-web-access/extract.ts +88 -13
- package/node_modules/pi-web-access/gemini-search.ts +10 -5
- package/node_modules/pi-web-access/github-extract.ts +47 -3
- package/node_modules/pi-web-access/index.ts +40 -40
- package/node_modules/pi-web-access/package.json +1 -1
- package/node_modules/pi-web-access/page-query.ts +61 -8
- package/node_modules/pi-web-access/xcrawl.ts +264 -0
- package/package.json +19 -22
- package/node_modules/@herbertgao/pi-stash/README.md +0 -34
- package/node_modules/@herbertgao/pi-stash/package.json +0 -51
- package/node_modules/@herbertgao/pi-stash/src/index.ts +0 -118
- package/node_modules/@tifan/pi-titlebar-spinner/README.md +0 -24
- package/node_modules/@tifan/pi-titlebar-spinner/package.json +0 -39
- package/node_modules/@tifan/pi-titlebar-spinner/src/index.ts +0 -49
|
@@ -207,7 +207,13 @@ function delimitedSubstitutionBody(command, start, opener, closer) {
|
|
|
207
207
|
}
|
|
208
208
|
return undefined;
|
|
209
209
|
}
|
|
210
|
-
|
|
210
|
+
const COMMIT_PUSH_MATCHER = {
|
|
211
|
+
id: "commit-push",
|
|
212
|
+
matchesVerb: (verb) => verb === "commit" || verb === "push",
|
|
213
|
+
indirectAlwaysMatches: true,
|
|
214
|
+
suppressPostVerbHelp: false,
|
|
215
|
+
};
|
|
216
|
+
function containsGuardedSubstitution(command, depth, matcher) {
|
|
211
217
|
if (depth > 3)
|
|
212
218
|
return false;
|
|
213
219
|
let quote;
|
|
@@ -249,8 +255,8 @@ function containsGuardedSubstitution(command, depth) {
|
|
|
249
255
|
: undefined;
|
|
250
256
|
if (substitution && substitution.end >= 0) {
|
|
251
257
|
const nested = canonicalizeGuardCommand(substitution.body);
|
|
252
|
-
if (containsGuardedSubstitution(nested, depth + 1) ||
|
|
253
|
-
tokenizeShellCommand(nested).some((segment) =>
|
|
258
|
+
if (containsGuardedSubstitution(nested, depth + 1, matcher) ||
|
|
259
|
+
tokenizeShellCommand(nested).some((segment) => containsGuardedGitVerb(segment.tokens, depth + 1, matcher))) {
|
|
254
260
|
return true;
|
|
255
261
|
}
|
|
256
262
|
i = substitution.end;
|
|
@@ -258,7 +264,169 @@ function containsGuardedSubstitution(command, depth) {
|
|
|
258
264
|
}
|
|
259
265
|
return false;
|
|
260
266
|
}
|
|
261
|
-
|
|
267
|
+
/** Git global options that consume the following token as their value. */
|
|
268
|
+
const GIT_GLOBAL_OPTIONS_WITH_VALUE = new Set([
|
|
269
|
+
"-C",
|
|
270
|
+
"-c",
|
|
271
|
+
"--config-env",
|
|
272
|
+
"--git-dir",
|
|
273
|
+
"--work-tree",
|
|
274
|
+
"--exec-path",
|
|
275
|
+
"--namespace",
|
|
276
|
+
]);
|
|
277
|
+
const GIT_GLOBAL_OPTIONS_WITH_INLINE_VALUE = [
|
|
278
|
+
"--config-env",
|
|
279
|
+
"--git-dir",
|
|
280
|
+
"--work-tree",
|
|
281
|
+
"--exec-path",
|
|
282
|
+
"--namespace",
|
|
283
|
+
];
|
|
284
|
+
/**
|
|
285
|
+
* Index of git's subcommand token in `gitTokens` (which starts at the `git`
|
|
286
|
+
* executable), after skipping global options and their values. Returns
|
|
287
|
+
* `undefined` when the invocation has no subcommand at all — `git --help`,
|
|
288
|
+
* `git --version`, or a trailing option with no verb behind it.
|
|
289
|
+
*
|
|
290
|
+
* Shared by the direct and indirect paths so both ask the same question about
|
|
291
|
+
* the same position (#2007).
|
|
292
|
+
*/
|
|
293
|
+
function gitVerbIndex(gitTokens) {
|
|
294
|
+
let i = 1;
|
|
295
|
+
while (i < gitTokens.length && gitTokens[i].startsWith("-")) {
|
|
296
|
+
const option = gitTokens[i];
|
|
297
|
+
if (["--help", "-h", "--version", "-v", "-V"].includes(option)) {
|
|
298
|
+
return undefined;
|
|
299
|
+
}
|
|
300
|
+
if (option === "--")
|
|
301
|
+
return i + 1 < gitTokens.length ? i + 1 : undefined;
|
|
302
|
+
if (["-C", "-c"].some((prefix) => option.startsWith(prefix) && option.length > prefix.length)) {
|
|
303
|
+
i += 1;
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
if (GIT_GLOBAL_OPTIONS_WITH_INLINE_VALUE.some((prefix) => option.startsWith(`${prefix}=`))) {
|
|
307
|
+
i += 1;
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
i += GIT_GLOBAL_OPTIONS_WITH_VALUE.has(option) ? 2 : 1;
|
|
311
|
+
}
|
|
312
|
+
return i < gitTokens.length ? i : undefined;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* True when the subcommand in git's command position is one the matcher
|
|
316
|
+
* governs. `gitTokens[0]` is the `git` executable token.
|
|
317
|
+
*/
|
|
318
|
+
export function matchGitVerbAtCommandPosition(gitTokens, matcher) {
|
|
319
|
+
const index = gitVerbIndex(gitTokens);
|
|
320
|
+
if (index === undefined)
|
|
321
|
+
return false;
|
|
322
|
+
const argsAfterVerb = gitTokens.slice(index + 1);
|
|
323
|
+
// `git checkout --help` prints documentation and touches nothing, and the
|
|
324
|
+
// global-option walk above cannot see it, because it sits AFTER the verb.
|
|
325
|
+
//
|
|
326
|
+
// Only the FIRST argument counts, and only for matchers that opt in. `-h`
|
|
327
|
+
// deeper in the argv may be an option VALUE rather than a help request —
|
|
328
|
+
// `git commit -m -h` commits with the message `-h`, `git clean -e -h`
|
|
329
|
+
// excludes a pattern named `-h`. Reading any position would need a
|
|
330
|
+
// per-verb table of value-taking options for every governed verb, which is
|
|
331
|
+
// exactly the hand-maintained parallel list this repo forbids, and every
|
|
332
|
+
// gap in it would silently UNDER-block. Leading-position-only needs no
|
|
333
|
+
// table, covers how help is actually invoked, and every other spelling
|
|
334
|
+
// falls through to a match, which is the safe direction for a guard.
|
|
335
|
+
if (matcher.suppressPostVerbHelp &&
|
|
336
|
+
(argsAfterVerb[0] === "--help" || argsAfterVerb[0] === "-h")) {
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
const verbs = expandGuardVerbToken(gitTokens[index] ?? "");
|
|
340
|
+
return verbs.length === 1 && matcher.matchesVerb(verbs[0], argsAfterVerb);
|
|
341
|
+
}
|
|
342
|
+
/** Strip one layer of shell quoting the lexer preserved on a value token. */
|
|
343
|
+
function unquoteGuardValue(value) {
|
|
344
|
+
const trimmed = value.trim();
|
|
345
|
+
if (trimmed.length >= 2) {
|
|
346
|
+
const first = trimmed[0];
|
|
347
|
+
const last = trimmed[trimmed.length - 1];
|
|
348
|
+
if ((first === '"' || first === "'") && first === last) {
|
|
349
|
+
return trimmed.slice(1, -1);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return trimmed;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* The directory a git invocation actually targets (#2007).
|
|
356
|
+
*
|
|
357
|
+
* `git -C <dir>` and `--work-tree` retarget the command at a DIFFERENT
|
|
358
|
+
* directory, so a guard that evaluates the caller's cwd would inspect the
|
|
359
|
+
* wrong working tree and allow a destructive command against a shared one.
|
|
360
|
+
* `-C` composes cumulatively, exactly as git applies it; `--work-tree` names
|
|
361
|
+
* the working tree directly and therefore wins.
|
|
362
|
+
*
|
|
363
|
+
* Returns the resolved absolute directory, or `cwd` when the invocation
|
|
364
|
+
* carries no retargeting option.
|
|
365
|
+
*/
|
|
366
|
+
export function resolveGitTargetDirectory(gitTokens, cwd) {
|
|
367
|
+
let base = cwd;
|
|
368
|
+
let workTree;
|
|
369
|
+
let i = 1;
|
|
370
|
+
while (i < gitTokens.length && gitTokens[i].startsWith("-")) {
|
|
371
|
+
const option = gitTokens[i];
|
|
372
|
+
if (["--help", "-h", "--version", "-v", "-V"].includes(option))
|
|
373
|
+
break;
|
|
374
|
+
if (option === "--")
|
|
375
|
+
break;
|
|
376
|
+
if (option === "-C" && i + 1 < gitTokens.length) {
|
|
377
|
+
base = path.resolve(base, unquoteGuardValue(gitTokens[i + 1]));
|
|
378
|
+
i += 2;
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
if (option.startsWith("-C") && option.length > 2) {
|
|
382
|
+
base = path.resolve(base, unquoteGuardValue(option.slice(2)));
|
|
383
|
+
i += 1;
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
if (option === "--work-tree" && i + 1 < gitTokens.length) {
|
|
387
|
+
workTree = unquoteGuardValue(gitTokens[i + 1]);
|
|
388
|
+
i += 2;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
if (option.startsWith("--work-tree=")) {
|
|
392
|
+
workTree = unquoteGuardValue(option.slice("--work-tree=".length));
|
|
393
|
+
i += 1;
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
if (option.startsWith("-c") &&
|
|
397
|
+
option.length > 2 &&
|
|
398
|
+
!option.startsWith("--")) {
|
|
399
|
+
i += 1;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
if (GIT_GLOBAL_OPTIONS_WITH_INLINE_VALUE.some((prefix) => option.startsWith(`${prefix}=`))) {
|
|
403
|
+
i += 1;
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
i += GIT_GLOBAL_OPTIONS_WITH_VALUE.has(option) ? 2 : 1;
|
|
407
|
+
}
|
|
408
|
+
return workTree === undefined ? base : path.resolve(base, workTree);
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Every git invocation the command contains, as token arrays starting at the
|
|
412
|
+
* `git` executable. Used by callers that must know WHICH directory a matched
|
|
413
|
+
* command targets, not merely that it matched (#2007).
|
|
414
|
+
*/
|
|
415
|
+
export function collectGitInvocations(toolName, input) {
|
|
416
|
+
if (toolName !== "bash")
|
|
417
|
+
return [];
|
|
418
|
+
const command = getShellCommand(input);
|
|
419
|
+
if (!command)
|
|
420
|
+
return [];
|
|
421
|
+
const invocations = [];
|
|
422
|
+
for (const segment of tokenizeShellCommand(canonicalizeGuardCommand(command))) {
|
|
423
|
+
const gitIndex = segment.tokens.findIndex((token) => isGitExecutable(token));
|
|
424
|
+
if (gitIndex >= 0)
|
|
425
|
+
invocations.push(segment.tokens.slice(gitIndex));
|
|
426
|
+
}
|
|
427
|
+
return invocations;
|
|
428
|
+
}
|
|
429
|
+
function containsGuardedGitVerb(tokens, depth, matcher) {
|
|
262
430
|
if (depth > 3 || tokens.length === 0)
|
|
263
431
|
return false;
|
|
264
432
|
let commandTokens = tokens;
|
|
@@ -276,7 +444,7 @@ function containsCommitOrPush(tokens, depth) {
|
|
|
276
444
|
const runIndex = lower.findIndex((token) => token === "--run");
|
|
277
445
|
if (runIndex >= 0 && runIndex + 1 < commandTokens.length) {
|
|
278
446
|
const nestedCommand = commandTokens.slice(runIndex + 2).join(" ");
|
|
279
|
-
return tokenizeShellCommand(canonicalizeGuardCommand(nestedCommand)).some((segment) =>
|
|
447
|
+
return tokenizeShellCommand(canonicalizeGuardCommand(nestedCommand)).some((segment) => containsGuardedGitVerb(segment.tokens, depth + 1, matcher));
|
|
280
448
|
}
|
|
281
449
|
}
|
|
282
450
|
// These commands consume their following words as text/patterns; a bare
|
|
@@ -290,43 +458,14 @@ function containsCommitOrPush(tokens, depth) {
|
|
|
290
458
|
if (gitIndex >= 0) {
|
|
291
459
|
// Any non-leading git invocation is indirect. Do not maintain a wrapper
|
|
292
460
|
// or flag allowlist: unknown launchers are the security boundary here.
|
|
293
|
-
if (gitIndex > 0)
|
|
461
|
+
if (gitIndex > 0 && matcher.indirectAlwaysMatches)
|
|
294
462
|
return true;
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
"--git-dir",
|
|
302
|
-
"--work-tree",
|
|
303
|
-
"--exec-path",
|
|
304
|
-
"--namespace",
|
|
305
|
-
]);
|
|
306
|
-
while (i < gitTokens.length && gitTokens[i].startsWith("-")) {
|
|
307
|
-
const option = gitTokens[i];
|
|
308
|
-
if (["--help", "-h", "--version", "-v", "-V"].includes(option))
|
|
309
|
-
return false;
|
|
310
|
-
if (option === "--")
|
|
311
|
-
return gitTokens[i + 1] === "commit" || gitTokens[i + 1] === "push";
|
|
312
|
-
if (["-C", "-c"].some((prefix) => option.startsWith(prefix) && option.length > prefix.length)) {
|
|
313
|
-
i += 1;
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
316
|
-
if ([
|
|
317
|
-
"--config-env",
|
|
318
|
-
"--git-dir",
|
|
319
|
-
"--work-tree",
|
|
320
|
-
"--exec-path",
|
|
321
|
-
"--namespace",
|
|
322
|
-
].some((prefix) => option.startsWith(`${prefix}=`))) {
|
|
323
|
-
i += 1;
|
|
324
|
-
continue;
|
|
325
|
-
}
|
|
326
|
-
i += takesValue.has(option) ? 2 : 1;
|
|
327
|
-
}
|
|
328
|
-
const verbs = expandGuardVerbToken(gitTokens[i] ?? "");
|
|
329
|
-
return verbs.length === 1 && (verbs[0] === "commit" || verbs[0] === "push");
|
|
463
|
+
// #2007: a guard that protects the agent from its own accident must not
|
|
464
|
+
// decline `xargs git status`, so the indirect path stays armed only when
|
|
465
|
+
// a governed verb sits in git's real COMMAND POSITION. Scanning every
|
|
466
|
+
// token instead would fire on `find . -name checkout | xargs git add`,
|
|
467
|
+
// where `checkout` is a positional value and not a subcommand at all.
|
|
468
|
+
return matchGitVerbAtCommandPosition(commandTokens.slice(gitIndex), matcher);
|
|
330
469
|
}
|
|
331
470
|
const leadingExecutable = commandTokens[0] ?? "";
|
|
332
471
|
const knownCommandStringWrapper = isShellWrapper(leadingExecutable) ||
|
|
@@ -344,7 +483,7 @@ function containsCommitOrPush(tokens, depth) {
|
|
|
344
483
|
if (commandIndex >= commandTokens.length)
|
|
345
484
|
return false;
|
|
346
485
|
const nestedCommand = commandTokens.slice(commandIndex).join(" ");
|
|
347
|
-
return tokenizeShellCommand(canonicalizeGuardCommand(nestedCommand)).some((segment) =>
|
|
486
|
+
return tokenizeShellCommand(canonicalizeGuardCommand(nestedCommand)).some((segment) => containsGuardedGitVerb(segment.tokens, depth + 1, matcher));
|
|
348
487
|
}
|
|
349
488
|
const lower = commandTokens.slice(1).map((token) => token.toLowerCase());
|
|
350
489
|
const switchIndex = lower.findIndex((token) => token === "-c" ||
|
|
@@ -369,19 +508,30 @@ function containsCommitOrPush(tokens, depth) {
|
|
|
369
508
|
if (commandTokens[commandIndex] === "--")
|
|
370
509
|
commandIndex += 1;
|
|
371
510
|
const nestedCommand = commandTokens.slice(commandIndex).join(" ");
|
|
372
|
-
return tokenizeShellCommand(canonicalizeGuardCommand(nestedCommand)).some((segment) =>
|
|
511
|
+
return tokenizeShellCommand(canonicalizeGuardCommand(nestedCommand)).some((segment) => containsGuardedGitVerb(segment.tokens, depth + 1, matcher));
|
|
373
512
|
}
|
|
374
|
-
/**
|
|
375
|
-
|
|
513
|
+
/**
|
|
514
|
+
* Analyze actual executable invocations, not substrings in shell text.
|
|
515
|
+
*
|
|
516
|
+
* Shared entry point for every guard that needs "is this bash input really a
|
|
517
|
+
* git invocation of a verb I govern" (#2007). The verb question is the only
|
|
518
|
+
* parameter; wrapper, substitution, and text-consumer analysis are identical
|
|
519
|
+
* for all callers by construction.
|
|
520
|
+
*/
|
|
521
|
+
export function detectGuardedGitVerb(toolName, input, matcher) {
|
|
376
522
|
if (toolName !== "bash")
|
|
377
523
|
return false;
|
|
378
524
|
const command = getShellCommand(input);
|
|
379
525
|
if (!command)
|
|
380
526
|
return false;
|
|
381
527
|
const canonical = canonicalizeGuardCommand(command);
|
|
382
|
-
if (containsGuardedSubstitution(canonical, 0))
|
|
528
|
+
if (containsGuardedSubstitution(canonical, 0, matcher))
|
|
383
529
|
return true;
|
|
384
|
-
return tokenizeShellCommand(canonical).some((segment) =>
|
|
530
|
+
return tokenizeShellCommand(canonical).some((segment) => containsGuardedGitVerb(segment.tokens, 0, matcher));
|
|
531
|
+
}
|
|
532
|
+
/** The #1063 commit gate's own question. */
|
|
533
|
+
export function isGitCommitOrPushAttempt(toolName, input) {
|
|
534
|
+
return detectGuardedGitVerb(toolName, input, COMMIT_PUSH_MATCHER);
|
|
385
535
|
}
|
|
386
536
|
function isTurnEndFindingsCache(value) {
|
|
387
537
|
if (!value || typeof value !== "object")
|
|
@@ -33,8 +33,25 @@
|
|
|
33
33
|
*/
|
|
34
34
|
import * as path from "node:path";
|
|
35
35
|
import { isExcludedDirName } from "./file-utils.js";
|
|
36
|
+
import { recordDegradationOnce } from "./degradation-ledger.js";
|
|
36
37
|
import { normalizeEphemeralMapKey, normalizeMapKey } from "./path-utils.js";
|
|
37
38
|
import { safeSpawnAsync } from "./safe-spawn.js";
|
|
39
|
+
import { truncatedByOutputCap } from "./spawn-output-cap.js";
|
|
40
|
+
function recordTruncatedLsFiles(parseSite) {
|
|
41
|
+
recordDegradationOnce({
|
|
42
|
+
kind: "git-tracked-ignore-truncated",
|
|
43
|
+
subject: `git-ls-files:${parseSite}`,
|
|
44
|
+
reason: "safeSpawnAsync capped git ls-files stdout",
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
// The binding listing is the untracked-ignored one: a mid-size project's
|
|
48
|
+
// node_modules alone yields a ~66k-line ignored list (roughly 4-6 MiB), and
|
|
49
|
+
// a monorepo with several dependency trees can multiply that. 16 MiB leaves
|
|
50
|
+
// that headroom while stopping safeSpawnAsync from retaining unbounded
|
|
51
|
+
// stdout (an uncapped probe held 20 MiB in full). The tracked listing is
|
|
52
|
+
// far smaller (~133 KiB in this repository). A capped read fails closed to
|
|
53
|
+
// unavailable with one bounded ledger record per site.
|
|
54
|
+
const MAX_LS_FILES_OUTPUT_BYTES = 16 * 1024 * 1024;
|
|
38
55
|
/**
|
|
39
56
|
* Parses `git ls-files --others --ignored --exclude-standard` output
|
|
40
57
|
* (repo-relative paths, one per line) into normalized map-key file ids.
|
|
@@ -62,7 +79,16 @@ export function parseUntrackedIgnoredOutput(stdout, cwd) {
|
|
|
62
79
|
}
|
|
63
80
|
async function fetchUntrackedIgnoredIds(cwd) {
|
|
64
81
|
try {
|
|
65
|
-
const result = await safeSpawnAsync("git", ["ls-files", "--others", "--ignored", "--exclude-standard"], {
|
|
82
|
+
const result = await safeSpawnAsync("git", ["ls-files", "--others", "--ignored", "--exclude-standard"], {
|
|
83
|
+
cwd,
|
|
84
|
+
timeout: 10_000,
|
|
85
|
+
maxOutputBytes: MAX_LS_FILES_OUTPUT_BYTES,
|
|
86
|
+
resourceLabel: "git-tracked-ignore",
|
|
87
|
+
});
|
|
88
|
+
if (truncatedByOutputCap(result)) {
|
|
89
|
+
recordTruncatedLsFiles("untracked-ignored");
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
66
92
|
if (result.error || result.status !== 0)
|
|
67
93
|
return undefined;
|
|
68
94
|
return parseUntrackedIgnoredOutput(result.stdout, cwd);
|
|
@@ -141,8 +167,13 @@ async function fetchTrackedFiles(cwd) {
|
|
|
141
167
|
const result = await safeSpawnAsync("git", ["ls-files"], {
|
|
142
168
|
cwd,
|
|
143
169
|
timeout: 10_000,
|
|
170
|
+
maxOutputBytes: MAX_LS_FILES_OUTPUT_BYTES,
|
|
144
171
|
resourceLabel: "git-tracked-ignore",
|
|
145
172
|
});
|
|
173
|
+
if (truncatedByOutputCap(result)) {
|
|
174
|
+
recordTruncatedLsFiles("tracked");
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
146
177
|
if (result.error || result.status !== 0)
|
|
147
178
|
return undefined;
|
|
148
179
|
return parseTrackedFilesOutput(result.stdout, cwd);
|
|
@@ -110,6 +110,7 @@ export class GovulncheckClient extends SecurityScanClient {
|
|
|
110
110
|
hostStallMs: goHostStallMs,
|
|
111
111
|
...(retryAfterMs > 0 && { retryAfterMs }),
|
|
112
112
|
budgetMs: 5000,
|
|
113
|
+
classifiedBy: "probe",
|
|
113
114
|
});
|
|
114
115
|
return false;
|
|
115
116
|
}
|
|
@@ -188,8 +189,10 @@ export class GovulncheckClient extends SecurityScanClient {
|
|
|
188
189
|
// row says so and carries the install facts behind it. Every retry
|
|
189
190
|
// DID run a `go install` that failed, so `install: "failed"` is
|
|
190
191
|
// earned, and the reason names the ceiling rather than the spawn.
|
|
192
|
+
// Below the ceiling, outcome/cause are fresh off `classifyProbeFailure`
|
|
193
|
+
// above, so that row is still `"probe"` (#2209).
|
|
194
|
+
classifiedBy: exhausted ? "caller" : "probe",
|
|
191
195
|
...(exhausted && {
|
|
192
|
-
classifiedBy: "caller",
|
|
193
196
|
evidence: {
|
|
194
197
|
...describeProbeEvidence(install, "go install"),
|
|
195
198
|
install: "failed",
|
|
@@ -313,6 +316,7 @@ export class GovulncheckClient extends SecurityScanClient {
|
|
|
313
316
|
hostStallMs: reprobeHostStallMs,
|
|
314
317
|
...(retryAfterMs > 0 && { retryAfterMs }),
|
|
315
318
|
budgetMs: 5000,
|
|
319
|
+
classifiedBy: "probe",
|
|
316
320
|
});
|
|
317
321
|
return false;
|
|
318
322
|
}
|
|
@@ -39,6 +39,10 @@ import { stagePathFor } from "./atomic-write-staging.js";
|
|
|
39
39
|
* to a main-thread rewrite on an error result).
|
|
40
40
|
*/
|
|
41
41
|
export async function writeGzipStageFile(data, stagePath, testDelayMs, options) {
|
|
42
|
+
// Worker-path duration starts inside this stage, so it includes the worker's
|
|
43
|
+
// test delay, serialization, gzip, and rename, but excludes postMessage queue
|
|
44
|
+
// latency. Main-thread callers measure from their inclusive persist entry.
|
|
45
|
+
const startedAt = performance.now();
|
|
42
46
|
const tmpPath = stagePathFor(stagePath);
|
|
43
47
|
try {
|
|
44
48
|
if (testDelayMs) {
|
|
@@ -56,6 +60,7 @@ export async function writeGzipStageFile(data, stagePath, testDelayMs, options)
|
|
|
56
60
|
gzBytes: 0,
|
|
57
61
|
serializeMs,
|
|
58
62
|
writeMs: 0,
|
|
63
|
+
durationMs: performance.now() - startedAt,
|
|
59
64
|
semanticFingerprint,
|
|
60
65
|
skippedUnchanged: true,
|
|
61
66
|
};
|
|
@@ -77,6 +82,7 @@ export async function writeGzipStageFile(data, stagePath, testDelayMs, options)
|
|
|
77
82
|
gzBytes,
|
|
78
83
|
serializeMs,
|
|
79
84
|
writeMs,
|
|
85
|
+
durationMs: performance.now() - startedAt,
|
|
80
86
|
semanticFingerprint,
|
|
81
87
|
};
|
|
82
88
|
}
|
|
@@ -116,6 +122,7 @@ export function serveGzipStageWorker(buildBaseResult, options) {
|
|
|
116
122
|
result.gzBytes = metrics.gzBytes;
|
|
117
123
|
result.serializeMs = metrics.serializeMs;
|
|
118
124
|
result.writeMs = metrics.writeMs;
|
|
125
|
+
result.durationMs = metrics.durationMs;
|
|
119
126
|
result.semanticFingerprint = metrics.semanticFingerprint;
|
|
120
127
|
result.skippedUnchanged = metrics.skippedUnchanged;
|
|
121
128
|
}
|