@the-open-engine/zeroshot 6.38.1 → 6.39.1
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 +6 -0
- package/cli/event-copy.js +8 -7
- package/cli/event-copy.ts +12 -0
- package/cli/index.js +67 -48
- package/cli/json-export.js +61 -64
- package/cli/json-export.ts +112 -0
- package/cli/message-formatter-utils.js +68 -56
- package/cli/message-formatter-utils.ts +121 -0
- package/cli/message-formatters-normal.js +124 -259
- package/cli/message-formatters-normal.ts +250 -0
- package/cli/message-formatters-watch.js +81 -84
- package/cli/message-formatters-watch.ts +189 -0
- package/lib/cluster/generated/protocol-schema.cjs +1 -1
- package/lib/cluster/generated/protocol-schema.mjs +1 -1
- package/lib/cluster/generated/protocol.d.cts +8 -0
- package/lib/cluster/generated/protocol.d.mts +8 -0
- package/lib/cluster/generated/protocol.d.ts +8 -0
- package/lib/clusters-registry.d.ts +23 -0
- package/lib/clusters-registry.js +17 -29
- package/lib/completion.d.ts +12 -0
- package/lib/completion.js +84 -94
- package/lib/compose-utils.d.ts +32 -0
- package/lib/compose-utils.js +57 -72
- package/lib/detached-startup.d.ts +84 -0
- package/lib/detached-startup.js +232 -302
- package/lib/docker-config.d.ts +43 -0
- package/lib/docker-config.js +150 -325
- package/lib/docker-env-auth.d.ts +17 -0
- package/lib/docker-env-auth.js +75 -0
- package/lib/git-remote-utils.d.ts +48 -0
- package/lib/git-remote-utils.js +147 -213
- package/lib/id-detector.d.ts +15 -0
- package/lib/id-detector.js +37 -41
- package/lib/path-check.d.ts +30 -0
- package/lib/path-check.js +42 -45
- package/lib/process-liveness.d.ts +16 -0
- package/lib/process-liveness.js +19 -12
- package/lib/provider-credential-path.d.ts +16 -0
- package/lib/provider-credential-path.js +31 -29
- package/lib/provider-defaults.d.ts +30 -0
- package/lib/provider-defaults.js +47 -42
- package/lib/provider-detection.d.ts +11 -0
- package/lib/provider-detection.js +91 -54
- package/lib/provider-names.d.ts +38 -0
- package/lib/provider-names.js +56 -64
- package/lib/repo-settings.d.ts +28 -0
- package/lib/repo-settings.js +83 -59
- package/lib/run-mode.d.ts +21 -0
- package/lib/run-mode.js +21 -20
- package/lib/run-plan.d.ts +28 -0
- package/lib/run-plan.js +17 -16
- package/lib/settings/claude-auth.d.ts +35 -0
- package/lib/settings/claude-auth.js +30 -47
- package/lib/settings-defaults.d.ts +20 -0
- package/lib/settings-defaults.js +149 -0
- package/lib/settings-error.d.ts +7 -0
- package/lib/settings-error.js +8 -0
- package/lib/settings-issue-providers.d.ts +10 -0
- package/lib/settings-issue-providers.js +12 -0
- package/lib/settings-models.d.ts +22 -0
- package/lib/settings-models.js +93 -0
- package/lib/settings-storage.d.ts +23 -0
- package/lib/settings-storage.js +205 -0
- package/lib/settings-validation.d.ts +7 -0
- package/lib/settings-validation.js +182 -0
- package/lib/settings.d.ts +29 -0
- package/lib/settings.js +41 -736
- package/lib/setup-apply-types.d.ts +66 -0
- package/lib/setup-apply-types.js +2 -0
- package/lib/setup-apply.d.ts +39 -0
- package/lib/setup-apply.js +267 -264
- package/lib/setup-journal.d.ts +41 -0
- package/lib/setup-journal.js +119 -76
- package/lib/setup-plan-types.d.ts +102 -0
- package/lib/setup-plan-types.js +2 -0
- package/lib/setup-plan.d.ts +30 -0
- package/lib/setup-plan.js +300 -324
- package/lib/setup-undo.d.ts +44 -0
- package/lib/setup-undo.js +61 -74
- package/lib/start-cluster-config.d.ts +26 -0
- package/lib/start-cluster-config.js +56 -0
- package/lib/start-cluster-environment.d.ts +41 -0
- package/lib/start-cluster-environment.js +134 -0
- package/lib/start-cluster-run-options.d.ts +54 -0
- package/lib/start-cluster-run-options.js +148 -0
- package/lib/start-cluster.d.ts +64 -0
- package/lib/start-cluster.js +98 -454
- package/lib/stream-json-parser.d.ts +17 -0
- package/lib/stream-json-parser.js +51 -52
- package/npm-shrinkwrap.json +6 -8
- package/package.json +14 -8
- package/scripts/postinstall.js +27 -0
- package/scripts/rust-distribution.js +36 -45
- package/src/agent/agent-command-proofs-context.js +35 -45
- package/src/agent/agent-command-proofs-context.ts +87 -0
- package/src/agent/agent-config-settings.js +19 -0
- package/src/agent/agent-config-settings.ts +41 -0
- package/src/agent/agent-config.js +122 -231
- package/src/agent/agent-config.ts +285 -0
- package/src/agent/agent-context-builder.js +38 -273
- package/src/agent/agent-context-builder.ts +51 -0
- package/src/agent/agent-context-environment-sections.js +105 -0
- package/src/agent/agent-context-environment-sections.ts +134 -0
- package/src/agent/agent-context-packs.js +165 -0
- package/src/agent/agent-context-packs.ts +296 -0
- package/src/agent/agent-context-prompt-sections.js +100 -0
- package/src/agent/agent-context-prompt-sections.ts +148 -0
- package/src/agent/agent-context-sections.js +25 -334
- package/src/agent/agent-context-sections.ts +36 -0
- package/src/agent/agent-context-sources.js +96 -149
- package/src/agent/agent-context-sources.ts +253 -0
- package/src/agent/agent-context-types.js +2 -0
- package/src/agent/agent-context-types.ts +132 -0
- package/src/agent/agent-context-validation-sections.js +73 -0
- package/src/agent/agent-context-validation-sections.ts +129 -0
- package/src/agent/agent-input-injector.js +99 -118
- package/src/agent/agent-input-injector.ts +195 -0
- package/src/agent/agent-liveness-poll.js +74 -78
- package/src/agent/agent-liveness-poll.ts +163 -0
- package/src/agent/agent-quality-gate-schema.js +77 -86
- package/src/agent/agent-quality-gate-schema.ts +135 -0
- package/src/agent/agent-quality-gates-context.js +44 -50
- package/src/agent/agent-quality-gates-context.ts +91 -0
- package/src/agent/agent-stuck-detector.js +171 -239
- package/src/agent/agent-stuck-detector.ts +288 -0
- package/src/agent/agent-trigger-evaluator.js +29 -46
- package/src/agent/agent-trigger-evaluator.ts +98 -0
- package/src/agent/context-metrics.js +110 -141
- package/src/agent/context-metrics.ts +261 -0
- package/src/agent/context-pack-builder.js +72 -358
- package/src/agent/context-pack-builder.ts +105 -0
- package/src/agent/context-pack-core.js +70 -0
- package/src/agent/context-pack-core.ts +96 -0
- package/src/agent/context-pack-selection.js +119 -0
- package/src/agent/context-pack-selection.ts +187 -0
- package/src/agent/context-pack-truncation.js +139 -0
- package/src/agent/context-pack-truncation.ts +208 -0
- package/src/agent/context-pack-types.js +2 -0
- package/src/agent/context-pack-types.ts +98 -0
- package/src/agent/context-replay-policy.js +30 -39
- package/src/agent/context-replay-policy.ts +71 -0
- package/src/agent/critical-agent-policy.js +6 -10
- package/src/agent/critical-agent-policy.ts +20 -0
- package/src/agent/guidance-queue.js +74 -87
- package/src/agent/guidance-queue.ts +166 -0
- package/src/agent/output-extraction-error-detail.js +45 -0
- package/src/agent/output-extraction-error-detail.ts +49 -0
- package/src/agent/output-extraction-failures.js +125 -0
- package/src/agent/output-extraction-failures.ts +149 -0
- package/src/agent/output-extraction-json.js +234 -0
- package/src/agent/output-extraction-json.ts +235 -0
- package/src/agent/output-extraction-pi.js +124 -0
- package/src/agent/output-extraction-pi.ts +137 -0
- package/src/agent/output-extraction-types.js +2 -0
- package/src/agent/output-extraction-types.ts +49 -0
- package/src/agent/output-extraction.js +17 -617
- package/src/agent/output-extraction.ts +30 -0
- package/src/agent/output-reformatter-errors.js +83 -0
- package/src/agent/output-reformatter-errors.ts +106 -0
- package/src/agent/output-reformatter-types.js +2 -0
- package/src/agent/output-reformatter-types.ts +86 -0
- package/src/agent/output-reformatter.js +140 -185
- package/src/agent/output-reformatter.ts +238 -0
- package/src/agent/pi-terminal-lifecycle-types.js +2 -0
- package/src/agent/pi-terminal-lifecycle-types.ts +41 -0
- package/src/agent/pi-terminal-lifecycle.js +186 -206
- package/src/agent/pi-terminal-lifecycle.ts +297 -0
- package/src/agent/provider-control-plane.js +22 -21
- package/src/agent/provider-control-plane.ts +52 -0
- package/src/agent/provider-terminal-failure-types.js +2 -0
- package/src/agent/provider-terminal-failure-types.ts +60 -0
- package/src/agent/provider-terminal-failure.js +194 -191
- package/src/agent/provider-terminal-failure.ts +262 -0
- package/src/agent/rate-limit-backoff.js +31 -74
- package/src/agent/rate-limit-backoff.ts +71 -0
- package/src/agent/schema-utils.js +85 -125
- package/src/agent/schema-utils.ts +148 -0
- package/src/agent/structured-output-error.js +34 -33
- package/src/agent/structured-output-error.ts +105 -0
- package/src/agent/validation-platform.js +27 -27
- package/src/agent/validation-platform.ts +49 -0
- package/src/agents/git-pusher-template.js +17 -34
- package/src/attach/attach-client.js +232 -405
- package/src/attach/attach-client.ts +300 -0
- package/src/attach/attach-server-cleanup.js +43 -0
- package/src/attach/attach-server-cleanup.ts +39 -0
- package/src/attach/attach-server-clients.js +117 -0
- package/src/attach/attach-server-clients.ts +122 -0
- package/src/attach/attach-server-events.js +38 -0
- package/src/attach/attach-server-events.ts +36 -0
- package/src/attach/attach-server-pty.js +45 -0
- package/src/attach/attach-server-pty.ts +64 -0
- package/src/attach/attach-server-runtime.js +73 -0
- package/src/attach/attach-server-runtime.ts +62 -0
- package/src/attach/attach-server-socket.js +28 -0
- package/src/attach/attach-server-socket.ts +23 -0
- package/src/attach/attach-server-types.js +2 -0
- package/src/attach/attach-server-types.ts +82 -0
- package/src/attach/attach-server.js +106 -523
- package/src/attach/attach-server.ts +158 -0
- package/src/attach/index.js +18 -35
- package/src/attach/index.ts +18 -0
- package/src/attach/protocol.js +95 -184
- package/src/attach/protocol.ts +181 -0
- package/src/attach/ring-buffer.js +76 -102
- package/src/attach/ring-buffer.ts +109 -0
- package/src/attach/send-input.js +64 -75
- package/src/attach/send-input.ts +94 -0
- package/src/attach/socket-discovery.js +164 -218
- package/src/attach/socket-discovery.ts +230 -0
- package/src/attach/socket-paths.js +42 -61
- package/src/attach/socket-paths.ts +81 -0
- package/src/claude-credentials.js +51 -54
- package/src/claude-credentials.ts +89 -0
- package/src/cluster/generated/protocol-schema.ts +1 -1
- package/src/cluster/generated/protocol.ts +2 -1
- package/src/command-proofs.js +110 -141
- package/src/command-proofs.ts +216 -0
- package/src/config-router.js +81 -73
- package/src/config-router.ts +131 -0
- package/src/copy-containment.js +191 -0
- package/src/copy-containment.ts +259 -0
- package/src/copy-worker.js +53 -30
- package/src/copy-worker.ts +88 -0
- package/src/darwin-keychain-boundary.js +80 -111
- package/src/darwin-keychain-boundary.ts +148 -0
- package/src/guidance-topics.js +4 -5
- package/src/guidance-topics.ts +10 -0
- package/src/input-helpers.js +33 -54
- package/src/input-helpers.ts +58 -0
- package/src/isolation-manager.js +59 -20
- package/src/ledger-sequence.js +45 -48
- package/src/ledger-sequence.ts +63 -0
- package/src/legacy-lib/clusters-registry.ts +40 -0
- package/src/legacy-lib/completion.ts +148 -0
- package/src/legacy-lib/compose-utils.ts +102 -0
- package/src/legacy-lib/detached-startup.ts +451 -0
- package/src/legacy-lib/docker-config.ts +282 -0
- package/src/legacy-lib/docker-env-auth.ts +116 -0
- package/src/legacy-lib/git-remote-utils.ts +244 -0
- package/src/legacy-lib/id-detector.ts +57 -0
- package/src/legacy-lib/js-yaml.d.ts +3 -0
- package/src/legacy-lib/omelette.d.ts +15 -0
- package/src/legacy-lib/path-check.ts +89 -0
- package/src/legacy-lib/process-liveness.ts +36 -0
- package/src/legacy-lib/provider-credential-path.ts +57 -0
- package/src/legacy-lib/provider-defaults.ts +114 -0
- package/src/legacy-lib/provider-detection.ts +67 -0
- package/src/legacy-lib/provider-names.ts +126 -0
- package/src/legacy-lib/repo-settings.ts +88 -0
- package/src/legacy-lib/run-mode.ts +58 -0
- package/src/legacy-lib/run-plan.ts +49 -0
- package/src/legacy-lib/settings/claude-auth.ts +80 -0
- package/src/legacy-lib/settings-defaults.ts +212 -0
- package/src/legacy-lib/settings-error.ts +8 -0
- package/src/legacy-lib/settings-issue-providers.ts +18 -0
- package/src/legacy-lib/settings-models.ts +124 -0
- package/src/legacy-lib/settings-storage.ts +277 -0
- package/src/legacy-lib/settings-validation.ts +220 -0
- package/src/legacy-lib/settings.ts +87 -0
- package/src/legacy-lib/setup-apply-types.ts +70 -0
- package/src/legacy-lib/setup-apply.ts +456 -0
- package/src/legacy-lib/setup-journal.ts +185 -0
- package/src/legacy-lib/setup-plan-types.ts +96 -0
- package/src/legacy-lib/setup-plan.ts +500 -0
- package/src/legacy-lib/setup-undo.ts +142 -0
- package/src/legacy-lib/start-cluster-config.ts +124 -0
- package/src/legacy-lib/start-cluster-environment.ts +166 -0
- package/src/legacy-lib/start-cluster-run-options.ts +281 -0
- package/src/legacy-lib/start-cluster.ts +237 -0
- package/src/legacy-lib/stream-json-parser.ts +88 -0
- package/src/message-buffer.js +55 -71
- package/src/message-buffer.ts +107 -0
- package/src/message-bus-bridge.js +92 -133
- package/src/message-bus-bridge.ts +149 -0
- package/src/name-generator-shared.js +220 -0
- package/src/name-generator-shared.ts +231 -0
- package/src/name-generator.js +8 -217
- package/src/name-generator.ts +27 -0
- package/src/omp-blob-root.js +83 -64
- package/src/omp-blob-root.ts +116 -0
- package/src/omp-config-overlay.js +65 -38
- package/src/omp-config-overlay.ts +110 -0
- package/src/omp-execution-fingerprint.js +38 -43
- package/src/omp-execution-fingerprint.ts +92 -0
- package/src/omp-session-limits.js +10 -15
- package/src/omp-session-limits.ts +53 -0
- package/src/omp-session-partition.js +2 -2
- package/src/omp-session-verifier.js +1 -1
- package/src/orchestrator.js +32 -7
- package/src/pr-body-template.js +71 -0
- package/src/providers/anthropic/index.js +3 -1
- package/src/providers/anthropic/index.ts +13 -0
- package/src/providers/capabilities.js +19 -27
- package/src/providers/capabilities.ts +44 -0
- package/src/providers/google/index.js +3 -1
- package/src/providers/google/index.ts +13 -0
- package/src/providers/openai/index.js +3 -1
- package/src/providers/openai/index.ts +13 -0
- package/src/providers/opencode/index.js +3 -1
- package/src/providers/opencode/index.ts +13 -0
- package/src/quality-gates.js +67 -127
- package/src/quality-gates.ts +149 -0
- package/src/repo-settings-access.js +40 -0
- package/src/repo-settings-access.ts +64 -0
- package/src/schemas/sub-cluster.js +127 -183
- package/src/schemas/sub-cluster.ts +238 -0
- package/src/state-snapshot-normalization.js +224 -0
- package/src/state-snapshot-normalization.ts +222 -0
- package/src/state-snapshot.js +192 -359
- package/src/state-snapshot.ts +291 -0
- package/src/state-snapshotter.js +118 -130
- package/src/state-snapshotter.ts +167 -0
- package/src/sub-cluster-wrapper.js +4 -13
- package/src/task-run-model-args.js +87 -124
- package/src/task-run-model-args.ts +204 -0
- package/src/task-runner.js +5 -25
- package/src/task-runner.ts +28 -0
- package/src/task-startup-error.js +73 -58
- package/src/task-startup-error.ts +126 -0
- package/src/template-validation/consensus-gate-contracts.js +2 -0
- package/src/template-validation/consensus-gate-contracts.ts +70 -0
- package/src/template-validation/consensus-gate-scenarios.js +76 -0
- package/src/template-validation/consensus-gate-scenarios.ts +130 -0
- package/src/template-validation/consensus-gate-stage.js +34 -0
- package/src/template-validation/consensus-gate-stage.ts +56 -0
- package/src/template-validation/random-topology-contracts.js +2 -0
- package/src/template-validation/random-topology-contracts.ts +54 -0
- package/src/template-validation/random-topology-dispatch.js +25 -0
- package/src/template-validation/random-topology-dispatch.ts +33 -0
- package/src/template-validation/random-topology-message-loop.js +53 -0
- package/src/template-validation/random-topology-message-loop.ts +73 -0
- package/src/template-validation/random-topology-operations.js +79 -0
- package/src/template-validation/random-topology-operations.ts +101 -0
- package/src/template-validation/random-topology-runtime.js +25 -0
- package/src/template-validation/random-topology-runtime.ts +43 -0
- package/src/template-validation/random-topology-scenario.js +52 -0
- package/src/template-validation/random-topology-scenario.ts +57 -0
- package/src/template-validation/random-topology-schema.js +159 -0
- package/src/template-validation/random-topology-schema.ts +190 -0
- package/src/template-validation/random-topology-state.js +28 -0
- package/src/template-validation/random-topology-state.ts +30 -0
- package/src/template-validation/random-topology-trigger-evaluation.js +45 -0
- package/src/template-validation/random-topology-trigger-evaluation.ts +65 -0
- package/src/template-validation/random-topology-trigger.js +75 -0
- package/src/template-validation/random-topology-trigger.ts +88 -0
- package/src/template-validation/report-formatter.js +40 -46
- package/src/template-validation/report-formatter.ts +101 -0
- package/src/template-validation/simulate-consensus-gates.js +111 -302
- package/src/template-validation/simulate-consensus-gates.ts +196 -0
- package/src/template-validation/simulate-random-topology.js +26 -519
- package/src/template-validation/simulate-random-topology.ts +57 -0
- package/src/template-validation/simulate-two-stage-validation.js +41 -265
- package/src/template-validation/simulate-two-stage-validation.ts +55 -0
- package/src/template-validation/simulation-agent-runtime.js +25 -0
- package/src/template-validation/simulation-agent-runtime.ts +40 -0
- package/src/template-validation/simulation-agent.js +29 -0
- package/src/template-validation/simulation-agent.ts +73 -0
- package/src/template-validation/simulation-runtime.js +34 -0
- package/src/template-validation/simulation-runtime.ts +72 -0
- package/src/template-validation/two-stage-contracts.js +2 -0
- package/src/template-validation/two-stage-contracts.ts +41 -0
- package/src/template-validation/two-stage-inputs.js +21 -0
- package/src/template-validation/two-stage-inputs.ts +29 -0
- package/src/template-validation/two-stage-results.js +30 -0
- package/src/template-validation/two-stage-results.ts +40 -0
- package/src/template-validation/two-stage-scenario.js +86 -0
- package/src/template-validation/two-stage-scenario.ts +120 -0
- package/src/worktree-claude-config.js +182 -220
- package/src/worktree-claude-config.ts +272 -0
- package/src/worktree-tooling-env.js +103 -125
- package/src/worktree-tooling-env.ts +142 -0
- package/task-lib/completion.js +2 -3
- package/task-lib/completion.ts +9 -0
- package/task-lib/config.js +1 -4
- package/task-lib/config.ts +11 -0
- package/task-lib/name-generator.js +4 -226
- package/task-lib/name-generator.ts +11 -0
- package/task-lib/omp-session-cleanup.js +1 -1
- package/task-lib/omp-storage-root.js +8 -12
- package/task-lib/omp-storage-root.ts +62 -0
- package/task-lib/process-termination.js +143 -166
- package/task-lib/process-termination.ts +268 -0
|
@@ -1,56 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
function isQualityGatesModule(value) {
|
|
3
|
+
return (typeof value === 'object' &&
|
|
4
|
+
value !== null &&
|
|
5
|
+
'normalizeRequiredQualityGates' in value &&
|
|
6
|
+
typeof value.normalizeRequiredQualityGates === 'function');
|
|
7
|
+
}
|
|
8
|
+
const qualityGatesModule = require('../quality-gates');
|
|
9
|
+
if (!isQualityGatesModule(qualityGatesModule)) {
|
|
10
|
+
throw new TypeError('quality-gates must export normalizeRequiredQualityGates');
|
|
11
|
+
}
|
|
12
|
+
const normalizeRequiredQualityGates = qualityGatesModule.normalizeRequiredQualityGates;
|
|
3
13
|
function appendGateDetails(lines, gate, index) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
lines.push(`${index + 1}. id: ${gate.id}${gate.scope ? `, scope: ${gate.scope}` : ''}`);
|
|
15
|
+
if (gate.description) {
|
|
16
|
+
lines.push(` description: ${gate.description}`);
|
|
17
|
+
}
|
|
18
|
+
if (gate.command) {
|
|
19
|
+
lines.push(` command: ${gate.command}`);
|
|
20
|
+
}
|
|
21
|
+
if (gate.profile || gate.proofProfile) {
|
|
22
|
+
lines.push(` cmdproof profile: ${gate.profile || gate.proofProfile}`);
|
|
23
|
+
lines.push(` cmdproof helper: zeroshot cmdproof check ${gate.id}`);
|
|
24
|
+
}
|
|
15
25
|
}
|
|
16
|
-
|
|
17
26
|
function buildRequiredQualityGatesSection(config) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
lines.push(
|
|
39
|
-
'',
|
|
40
|
-
'For each configured gate:',
|
|
41
|
-
'- Run the configured command when one is provided by repo or cluster config.',
|
|
42
|
-
'- For gates with a cmdproof profile, run `zeroshot cmdproof check <id>` before considering a raw command.',
|
|
43
|
-
'- Put the command, numeric exit code, and string output in `evidence`.',
|
|
44
|
-
'- Set `status` to `PASS` only when the gate completes successfully.',
|
|
45
|
-
'- If a required gate fails, set `approved` to false and publish status `FAIL`.',
|
|
46
|
-
'- If a required gate cannot run because its command, tool, or service is unavailable, set `approved` to false and publish status `UNAVAILABLE`.',
|
|
47
|
-
'- Use `completedAt` or `timestamp` from the current validation run; stale evidence must be marked with `stale: true`.',
|
|
48
|
-
''
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
return lines.join('\n');
|
|
27
|
+
if (config.role !== 'validator') {
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
const gates = normalizeRequiredQualityGates(config.requiredQualityGates);
|
|
31
|
+
if (gates.length === 0) {
|
|
32
|
+
return '';
|
|
33
|
+
}
|
|
34
|
+
const lines = [
|
|
35
|
+
'## Required Handoff Quality Gates',
|
|
36
|
+
'',
|
|
37
|
+
'Before approving implementation handoff, publish one `qualityGates` entry for each configured required gate.',
|
|
38
|
+
'Each entry must use the configured `id` and `scope` when a scope is configured.',
|
|
39
|
+
'',
|
|
40
|
+
'Configured gates:',
|
|
41
|
+
];
|
|
42
|
+
gates.forEach((gate, index) => appendGateDetails(lines, gate, index));
|
|
43
|
+
lines.push('', 'For each configured gate:', '- Run the configured command when one is provided by repo or cluster config.', '- For gates with a cmdproof profile, run `zeroshot cmdproof check <id>` before considering a raw command.', '- Put the command, numeric exit code, and string output in `evidence`.', '- Set `status` to `PASS` only when the gate completes successfully.', '- If a required gate fails, set `approved` to false and publish status `FAIL`.', '- If a required gate cannot run because its command, tool, or service is unavailable, ' +
|
|
44
|
+
'set `approved` to false and publish status `UNAVAILABLE`.', '- Use `completedAt` or `timestamp` from the current validation run; ' +
|
|
45
|
+
'stale evidence must be marked with `stale: true`.', '');
|
|
46
|
+
return lines.join('\n');
|
|
52
47
|
}
|
|
53
|
-
|
|
54
48
|
module.exports = {
|
|
55
|
-
|
|
49
|
+
buildRequiredQualityGatesSection,
|
|
56
50
|
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
type StringList = string[];
|
|
2
|
+
|
|
3
|
+
interface RequiredQualityGate {
|
|
4
|
+
id: string;
|
|
5
|
+
scope?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
command?: string;
|
|
8
|
+
profile?: string;
|
|
9
|
+
proofProfile?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface AgentQualityGateConfig {
|
|
13
|
+
role?: string;
|
|
14
|
+
requiredQualityGates?: unknown;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface QualityGatesModule {
|
|
18
|
+
normalizeRequiredQualityGates(value: unknown): RequiredQualityGate[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isQualityGatesModule(value: unknown): value is QualityGatesModule {
|
|
22
|
+
return (
|
|
23
|
+
typeof value === 'object' &&
|
|
24
|
+
value !== null &&
|
|
25
|
+
'normalizeRequiredQualityGates' in value &&
|
|
26
|
+
typeof value.normalizeRequiredQualityGates === 'function'
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const qualityGatesModule: unknown = require('../quality-gates');
|
|
31
|
+
if (!isQualityGatesModule(qualityGatesModule)) {
|
|
32
|
+
throw new TypeError('quality-gates must export normalizeRequiredQualityGates');
|
|
33
|
+
}
|
|
34
|
+
const normalizeRequiredQualityGates = qualityGatesModule.normalizeRequiredQualityGates;
|
|
35
|
+
|
|
36
|
+
function appendGateDetails(lines: StringList, gate: RequiredQualityGate, index: number): void {
|
|
37
|
+
lines.push(`${index + 1}. id: ${gate.id}${gate.scope ? `, scope: ${gate.scope}` : ''}`);
|
|
38
|
+
if (gate.description) {
|
|
39
|
+
lines.push(` description: ${gate.description}`);
|
|
40
|
+
}
|
|
41
|
+
if (gate.command) {
|
|
42
|
+
lines.push(` command: ${gate.command}`);
|
|
43
|
+
}
|
|
44
|
+
if (gate.profile || gate.proofProfile) {
|
|
45
|
+
lines.push(` cmdproof profile: ${gate.profile || gate.proofProfile}`);
|
|
46
|
+
lines.push(` cmdproof helper: zeroshot cmdproof check ${gate.id}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function buildRequiredQualityGatesSection(config: AgentQualityGateConfig): string {
|
|
51
|
+
if (config.role !== 'validator') {
|
|
52
|
+
return '';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const gates = normalizeRequiredQualityGates(config.requiredQualityGates);
|
|
56
|
+
if (gates.length === 0) {
|
|
57
|
+
return '';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const lines = [
|
|
61
|
+
'## Required Handoff Quality Gates',
|
|
62
|
+
'',
|
|
63
|
+
'Before approving implementation handoff, publish one `qualityGates` entry for each configured required gate.',
|
|
64
|
+
'Each entry must use the configured `id` and `scope` when a scope is configured.',
|
|
65
|
+
'',
|
|
66
|
+
'Configured gates:',
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
gates.forEach((gate, index) => appendGateDetails(lines, gate, index));
|
|
70
|
+
|
|
71
|
+
lines.push(
|
|
72
|
+
'',
|
|
73
|
+
'For each configured gate:',
|
|
74
|
+
'- Run the configured command when one is provided by repo or cluster config.',
|
|
75
|
+
'- For gates with a cmdproof profile, run `zeroshot cmdproof check <id>` before considering a raw command.',
|
|
76
|
+
'- Put the command, numeric exit code, and string output in `evidence`.',
|
|
77
|
+
'- Set `status` to `PASS` only when the gate completes successfully.',
|
|
78
|
+
'- If a required gate fails, set `approved` to false and publish status `FAIL`.',
|
|
79
|
+
'- If a required gate cannot run because its command, tool, or service is unavailable, ' +
|
|
80
|
+
'set `approved` to false and publish status `UNAVAILABLE`.',
|
|
81
|
+
'- Use `completedAt` or `timestamp` from the current validation run; ' +
|
|
82
|
+
'stale evidence must be marked with `stale: true`.',
|
|
83
|
+
''
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
return lines.join('\n');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export = {
|
|
90
|
+
buildRequiredQualityGatesSection,
|
|
91
|
+
};
|
|
@@ -1,256 +1,188 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
*
|
|
23
|
-
* Threshold: stuckScore >= 3.5 = likely stuck
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
const { execSync } = require('../lib/safe-exec'); // Enforces timeouts
|
|
27
|
-
const fs = require('fs');
|
|
28
|
-
|
|
29
|
-
// Stuck detection thresholds
|
|
30
|
-
const STUCK_THRESHOLD = 3.5; // Score at which we consider process stuck
|
|
1
|
+
"use strict";
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
function isSafeExecModule(value) {
|
|
4
|
+
return (typeof value === 'object' &&
|
|
5
|
+
value !== null &&
|
|
6
|
+
'execSync' in value &&
|
|
7
|
+
typeof value.execSync === 'function');
|
|
8
|
+
}
|
|
9
|
+
function errorMessage(error) {
|
|
10
|
+
if (typeof error === 'object' && error !== null && 'message' in error) {
|
|
11
|
+
return error.message;
|
|
12
|
+
}
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
function parseProcInteger(value) {
|
|
16
|
+
return value === undefined ? Number.NaN : parseInt(value, 10);
|
|
17
|
+
}
|
|
18
|
+
function hasSocketFields(fields) {
|
|
19
|
+
return fields?.length === 5;
|
|
20
|
+
}
|
|
21
|
+
const STUCK_THRESHOLD = 3.5;
|
|
31
22
|
const HIGH_CONFIDENCE_THRESHOLD = 4.5;
|
|
32
|
-
const CPU_LOW_THRESHOLD = 1;
|
|
33
|
-
const CTX_SWITCHES_LOW_THRESHOLD = 10;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
23
|
+
const CPU_LOW_THRESHOLD = 1;
|
|
24
|
+
const CTX_SWITCHES_LOW_THRESHOLD = 10;
|
|
25
|
+
function stuckScoreFor(indicators, hasDataInFlight) {
|
|
26
|
+
const score = Number(indicators.isSleeping) +
|
|
27
|
+
Number(indicators.isBlockedOnPoll) +
|
|
28
|
+
Number(indicators.lowCpuUsage) +
|
|
29
|
+
Number(indicators.lowCtxSwitches) +
|
|
30
|
+
Number(indicators.noDataInFlight) * 0.5 +
|
|
31
|
+
Number(Boolean(indicators.hasSynSent));
|
|
32
|
+
return hasDataInFlight ? Math.max(0, score - 2) : score;
|
|
33
|
+
}
|
|
34
|
+
function confidenceFor(stuckScore) {
|
|
35
|
+
if (stuckScore >= HIGH_CONFIDENCE_THRESHOLD)
|
|
36
|
+
return 'high';
|
|
37
|
+
if (stuckScore >= STUCK_THRESHOLD)
|
|
38
|
+
return 'medium';
|
|
39
|
+
return 'low';
|
|
40
|
+
}
|
|
41
|
+
const rawSafeExec = require('../lib/safe-exec');
|
|
42
|
+
if (!isSafeExecModule(rawSafeExec)) {
|
|
43
|
+
throw new TypeError('safe-exec must export execSync');
|
|
44
|
+
}
|
|
45
|
+
const { execSync } = rawSafeExec;
|
|
40
46
|
function getProcessState(pid) {
|
|
41
|
-
try {
|
|
42
|
-
const statPath = `/proc/${pid}/stat`;
|
|
43
|
-
if (!fs.existsSync(statPath)) {
|
|
44
|
-
return { exists: false };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const stat = fs.readFileSync(statPath, 'utf8');
|
|
48
|
-
const parts = stat.split(' ');
|
|
49
|
-
|
|
50
|
-
// stat fields: pid, comm, state, ppid, pgrp, ...
|
|
51
|
-
// State is the 3rd field (index 2): R=running, S=sleeping, D=disk sleep, Z=zombie
|
|
52
|
-
const state = parts[2];
|
|
53
|
-
|
|
54
|
-
// Get wchan (what the process is waiting on)
|
|
55
|
-
let wchan = '';
|
|
56
47
|
try {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
48
|
+
const statPath = `/proc/${pid}/stat`;
|
|
49
|
+
if (!fs.existsSync(statPath)) {
|
|
50
|
+
return { exists: false };
|
|
51
|
+
}
|
|
52
|
+
const stat = fs.readFileSync(statPath, 'utf8');
|
|
53
|
+
const parts = stat.split(' ');
|
|
54
|
+
const state = parts[2];
|
|
55
|
+
let wchan = '';
|
|
56
|
+
try {
|
|
57
|
+
wchan = fs.readFileSync(`/proc/${pid}/wchan`, 'utf8').trim();
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// wchan may not be readable.
|
|
61
|
+
}
|
|
62
|
+
const utime = parseProcInteger(parts[13]);
|
|
63
|
+
const stime = parseProcInteger(parts[14]);
|
|
64
|
+
const status = fs.readFileSync(`/proc/${pid}/status`, 'utf8');
|
|
65
|
+
const threads = /Threads:\s+(\d+)/.exec(status)?.[1] || '1';
|
|
66
|
+
const volCtxSwitches = /voluntary_ctxt_switches:\s+(\d+)/.exec(status)?.[1] || '0';
|
|
67
|
+
return {
|
|
68
|
+
exists: true,
|
|
69
|
+
state,
|
|
70
|
+
wchan,
|
|
71
|
+
cpuTicks: utime + stime,
|
|
72
|
+
threads: parseInt(threads, 10),
|
|
73
|
+
volCtxSwitches: parseInt(volCtxSwitches, 10),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
return { exists: false, error: errorMessage(error) };
|
|
60
78
|
}
|
|
61
|
-
|
|
62
|
-
// Get CPU usage from stat
|
|
63
|
-
// utime (field 14) + stime (field 15) = total CPU ticks
|
|
64
|
-
const utime = parseInt(parts[13], 10);
|
|
65
|
-
const stime = parseInt(parts[14], 10);
|
|
66
|
-
|
|
67
|
-
// Get status for more info
|
|
68
|
-
const status = fs.readFileSync(`/proc/${pid}/status`, 'utf8');
|
|
69
|
-
const threads = status.match(/Threads:\s+(\d+)/)?.[1] || '1';
|
|
70
|
-
const volCtxSwitches = status.match(/voluntary_ctxt_switches:\s+(\d+)/)?.[1] || '0';
|
|
71
|
-
|
|
72
|
-
return {
|
|
73
|
-
exists: true,
|
|
74
|
-
state,
|
|
75
|
-
wchan,
|
|
76
|
-
cpuTicks: utime + stime,
|
|
77
|
-
threads: parseInt(threads, 10),
|
|
78
|
-
volCtxSwitches: parseInt(volCtxSwitches, 10),
|
|
79
|
-
};
|
|
80
|
-
} catch (err) {
|
|
81
|
-
return { exists: false, error: err.message };
|
|
82
|
-
}
|
|
83
79
|
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Get network socket activity for a process
|
|
87
|
-
* @param {number} pid - Process ID
|
|
88
|
-
* @returns {object} Network state info
|
|
89
|
-
*/
|
|
90
80
|
function getNetworkState(pid) {
|
|
91
|
-
try {
|
|
92
|
-
const fdPath = `/proc/${pid}/fd`;
|
|
93
|
-
if (!fs.existsSync(fdPath)) {
|
|
94
|
-
return { hasNetwork: false };
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Use ss to get socket states for this process
|
|
98
|
-
let ssOutput = '';
|
|
99
81
|
try {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
82
|
+
const fdPath = `/proc/${pid}/fd`;
|
|
83
|
+
if (!fs.existsSync(fdPath)) {
|
|
84
|
+
return { hasNetwork: false };
|
|
85
|
+
}
|
|
86
|
+
let ssOutput = '';
|
|
87
|
+
try {
|
|
88
|
+
ssOutput = execSync(`ss -tunp 2>/dev/null | grep ",pid=${pid}," || true`, {
|
|
89
|
+
encoding: 'utf8',
|
|
90
|
+
timeout: 5000,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return { hasNetwork: false };
|
|
95
|
+
}
|
|
96
|
+
if (!ssOutput.trim()) {
|
|
97
|
+
return { hasNetwork: false, connections: [] };
|
|
98
|
+
}
|
|
99
|
+
const connections = [];
|
|
100
|
+
for (const line of ssOutput.trim().split('\n')) {
|
|
101
|
+
const match = /^(\S+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)/.exec(line);
|
|
102
|
+
const fields = match?.slice(1, 6);
|
|
103
|
+
if (!hasSocketFields(fields))
|
|
104
|
+
continue;
|
|
105
|
+
const [state, recvQ, sendQ, local, peer] = fields;
|
|
106
|
+
connections.push({
|
|
107
|
+
state,
|
|
108
|
+
recvQ: parseInt(recvQ, 10),
|
|
109
|
+
sendQ: parseInt(sendQ, 10),
|
|
110
|
+
local,
|
|
111
|
+
peer,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
hasNetwork: connections.length > 0,
|
|
116
|
+
connections,
|
|
117
|
+
establishedCount: connections.filter((connection) => connection.state === 'ESTAB').length,
|
|
118
|
+
hasDataInFlight: connections.some((connection) => connection.recvQ > 0 || connection.sendQ > 0),
|
|
119
|
+
hasSynSent: connections.some((connection) => connection.state === 'SYN-SENT'),
|
|
120
|
+
};
|
|
106
121
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return { hasNetwork: false, connections: [] };
|
|
122
|
+
catch (error) {
|
|
123
|
+
return { hasNetwork: false, error: errorMessage(error) };
|
|
110
124
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
// Parse ss output: State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
|
|
117
|
-
const match = line.match(/^(\S+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)/);
|
|
118
|
-
if (match) {
|
|
119
|
-
connections.push({
|
|
120
|
-
state: match[1],
|
|
121
|
-
recvQ: parseInt(match[2], 10),
|
|
122
|
-
sendQ: parseInt(match[3], 10),
|
|
123
|
-
local: match[4],
|
|
124
|
-
peer: match[5],
|
|
125
|
-
});
|
|
126
|
-
}
|
|
125
|
+
}
|
|
126
|
+
async function analyzeProcessHealth(pid, samplePeriodMs = 5000) {
|
|
127
|
+
const t0 = getProcessState(pid);
|
|
128
|
+
if (!t0.exists) {
|
|
129
|
+
return { isLikelyStuck: null, reason: 'Process does not exist', pid };
|
|
127
130
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
await new Promise((resolve) => setTimeout(resolve, samplePeriodMs));
|
|
132
|
+
const t1 = getProcessState(pid);
|
|
133
|
+
if (!t1.exists) {
|
|
134
|
+
return { isLikelyStuck: null, reason: 'Process died during analysis', pid };
|
|
135
|
+
}
|
|
136
|
+
const cpuTicksDelta = t1.cpuTicks - t0.cpuTicks;
|
|
137
|
+
const ctxSwitchesDelta = t1.volCtxSwitches - t0.volCtxSwitches;
|
|
138
|
+
const cpuSeconds = cpuTicksDelta / 100;
|
|
139
|
+
const sampleSeconds = samplePeriodMs / 1000;
|
|
140
|
+
const cpuPercent = (cpuSeconds / sampleSeconds) * 100;
|
|
141
|
+
const network = getNetworkState(pid);
|
|
142
|
+
const indicators = {
|
|
143
|
+
isSleeping: t1.state === 'S',
|
|
144
|
+
isBlockedOnPoll: t1.wchan.includes('poll') || t1.wchan.includes('wait'),
|
|
145
|
+
lowCpuUsage: cpuPercent < CPU_LOW_THRESHOLD,
|
|
146
|
+
lowCtxSwitches: ctxSwitchesDelta < CTX_SWITCHES_LOW_THRESHOLD,
|
|
147
|
+
noDataInFlight: network.hasNetwork && !network.hasDataInFlight,
|
|
148
|
+
hasSynSent: network.hasSynSent,
|
|
149
|
+
};
|
|
150
|
+
const stuckScore = stuckScoreFor(indicators, network.hasDataInFlight);
|
|
151
|
+
const isLikelyStuck = stuckScore >= STUCK_THRESHOLD;
|
|
152
|
+
const confidence = confidenceFor(stuckScore);
|
|
134
153
|
return {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
154
|
+
pid,
|
|
155
|
+
state: t1.state,
|
|
156
|
+
wchan: t1.wchan,
|
|
157
|
+
cpuPercent: parseFloat(cpuPercent.toFixed(2)),
|
|
158
|
+
ctxSwitchesDelta,
|
|
159
|
+
threads: t1.threads,
|
|
160
|
+
network: {
|
|
161
|
+
hasConnections: network.hasNetwork,
|
|
162
|
+
establishedCount: network.establishedCount || 0,
|
|
163
|
+
hasDataInFlight: network.hasDataInFlight || false,
|
|
164
|
+
hasSynSent: network.hasSynSent || false,
|
|
165
|
+
},
|
|
166
|
+
indicators,
|
|
167
|
+
stuckScore: parseFloat(stuckScore.toFixed(1)),
|
|
168
|
+
isLikelyStuck,
|
|
169
|
+
confidence,
|
|
170
|
+
analysis: isLikelyStuck
|
|
171
|
+
? `Process appears STUCK: sleeping on ${t1.wchan}, ${cpuPercent.toFixed(1)}% CPU, ` +
|
|
172
|
+
`${ctxSwitchesDelta} ctx switches`
|
|
173
|
+
: `Process appears WORKING: ${cpuPercent.toFixed(1)}% CPU, ${ctxSwitchesDelta} ctx switches, state=${t1.state}`,
|
|
140
174
|
};
|
|
141
|
-
} catch (err) {
|
|
142
|
-
return { hasNetwork: false, error: err.message };
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Analyze process health using multi-indicator approach
|
|
148
|
-
*
|
|
149
|
-
* @param {number} pid - Process ID
|
|
150
|
-
* @param {number} samplePeriodMs - How long to sample (default 5000ms)
|
|
151
|
-
* @returns {Promise<object>} Analysis result with isLikelyStuck, stuckScore, indicators
|
|
152
|
-
*/
|
|
153
|
-
async function analyzeProcessHealth(pid, samplePeriodMs = 5000) {
|
|
154
|
-
const t0 = getProcessState(pid);
|
|
155
|
-
if (!t0.exists) {
|
|
156
|
-
return { isLikelyStuck: null, reason: 'Process does not exist', pid };
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Wait and sample again
|
|
160
|
-
await new Promise((r) => setTimeout(r, samplePeriodMs));
|
|
161
|
-
|
|
162
|
-
const t1 = getProcessState(pid);
|
|
163
|
-
if (!t1.exists) {
|
|
164
|
-
return { isLikelyStuck: null, reason: 'Process died during analysis', pid };
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Calculate CPU usage during sample period
|
|
168
|
-
const cpuTicksDelta = t1.cpuTicks - t0.cpuTicks;
|
|
169
|
-
const ctxSwitchesDelta = t1.volCtxSwitches - t0.volCtxSwitches;
|
|
170
|
-
|
|
171
|
-
// Get clock ticks per second (typically 100 on Linux)
|
|
172
|
-
const clockTicks = 100;
|
|
173
|
-
|
|
174
|
-
// CPU seconds used during sample
|
|
175
|
-
const cpuSeconds = cpuTicksDelta / clockTicks;
|
|
176
|
-
const sampleSeconds = samplePeriodMs / 1000;
|
|
177
|
-
const cpuPercent = (cpuSeconds / sampleSeconds) * 100;
|
|
178
|
-
|
|
179
|
-
// Get network state
|
|
180
|
-
const network = getNetworkState(pid);
|
|
181
|
-
|
|
182
|
-
// Analyze stuck indicators
|
|
183
|
-
const indicators = {
|
|
184
|
-
isSleeping: t1.state === 'S',
|
|
185
|
-
isBlockedOnPoll: t1.wchan.includes('poll') || t1.wchan.includes('wait'),
|
|
186
|
-
lowCpuUsage: cpuPercent < CPU_LOW_THRESHOLD,
|
|
187
|
-
lowCtxSwitches: ctxSwitchesDelta < CTX_SWITCHES_LOW_THRESHOLD,
|
|
188
|
-
// Network indicators (only apply if process has network connections)
|
|
189
|
-
noDataInFlight: network.hasNetwork && !network.hasDataInFlight,
|
|
190
|
-
hasSynSent: network.hasSynSent, // Stuck trying to connect
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
// Calculate stuck score using weighted indicators
|
|
194
|
-
let stuckScore = 0;
|
|
195
|
-
if (indicators.isSleeping) stuckScore += 1;
|
|
196
|
-
if (indicators.isBlockedOnPoll) stuckScore += 1;
|
|
197
|
-
if (indicators.lowCpuUsage) stuckScore += 1;
|
|
198
|
-
if (indicators.lowCtxSwitches) stuckScore += 1;
|
|
199
|
-
if (indicators.noDataInFlight) stuckScore += 0.5; // Secondary signal
|
|
200
|
-
if (indicators.hasSynSent) stuckScore += 1; // Strong signal - stuck connecting
|
|
201
|
-
|
|
202
|
-
// CRITICAL: If data IS flowing, REDUCE stuck score (legitimate work)
|
|
203
|
-
if (network.hasDataInFlight) {
|
|
204
|
-
stuckScore = Math.max(0, stuckScore - 2); // Active I/O = likely working
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const isLikelyStuck = stuckScore >= STUCK_THRESHOLD;
|
|
208
|
-
let confidence = 'low';
|
|
209
|
-
if (stuckScore >= HIGH_CONFIDENCE_THRESHOLD) {
|
|
210
|
-
confidence = 'high';
|
|
211
|
-
} else if (stuckScore >= STUCK_THRESHOLD) {
|
|
212
|
-
confidence = 'medium';
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return {
|
|
216
|
-
pid,
|
|
217
|
-
state: t1.state,
|
|
218
|
-
wchan: t1.wchan,
|
|
219
|
-
cpuPercent: parseFloat(cpuPercent.toFixed(2)),
|
|
220
|
-
ctxSwitchesDelta,
|
|
221
|
-
threads: t1.threads,
|
|
222
|
-
network: {
|
|
223
|
-
hasConnections: network.hasNetwork,
|
|
224
|
-
establishedCount: network.establishedCount || 0,
|
|
225
|
-
hasDataInFlight: network.hasDataInFlight || false,
|
|
226
|
-
hasSynSent: network.hasSynSent || false,
|
|
227
|
-
},
|
|
228
|
-
indicators,
|
|
229
|
-
stuckScore: parseFloat(stuckScore.toFixed(1)),
|
|
230
|
-
isLikelyStuck,
|
|
231
|
-
confidence,
|
|
232
|
-
analysis: isLikelyStuck
|
|
233
|
-
? `Process appears STUCK: sleeping on ${t1.wchan}, ${cpuPercent.toFixed(1)}% CPU, ${ctxSwitchesDelta} ctx switches`
|
|
234
|
-
: `Process appears WORKING: ${cpuPercent.toFixed(1)}% CPU, ${ctxSwitchesDelta} ctx switches, state=${t1.state}`,
|
|
235
|
-
};
|
|
236
175
|
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Check if we're on a platform that supports /proc filesystem
|
|
240
|
-
* @returns {boolean}
|
|
241
|
-
*/
|
|
242
176
|
function isPlatformSupported() {
|
|
243
|
-
|
|
177
|
+
return process.platform === 'linux' && fs.existsSync('/proc');
|
|
244
178
|
}
|
|
245
|
-
|
|
246
179
|
module.exports = {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
CTX_SWITCHES_LOW_THRESHOLD,
|
|
180
|
+
analyzeProcessHealth,
|
|
181
|
+
getProcessState,
|
|
182
|
+
getNetworkState,
|
|
183
|
+
isPlatformSupported,
|
|
184
|
+
STUCK_THRESHOLD,
|
|
185
|
+
HIGH_CONFIDENCE_THRESHOLD,
|
|
186
|
+
CPU_LOW_THRESHOLD,
|
|
187
|
+
CTX_SWITCHES_LOW_THRESHOLD,
|
|
256
188
|
};
|