@jevitate/cli 0.1.0 → 0.3.0
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 +23 -0
- package/dist/ai-cli.d.ts +31 -11
- package/dist/ai-cli.js +131 -88
- package/dist/ai-cli.js.map +1 -1
- package/dist/author-journey-cli.d.ts +4 -0
- package/dist/author-journey-cli.js +111 -0
- package/dist/author-journey-cli.js.map +1 -0
- package/dist/bin.js +60349 -16477
- package/dist/bin.js.map +1 -1
- package/dist/browser-run-options.d.ts +89 -0
- package/dist/browser-run-options.js +175 -0
- package/dist/browser-run-options.js.map +1 -0
- package/dist/build-info.generated.d.ts +2 -0
- package/dist/build-info.generated.js +10 -0
- package/dist/build-info.generated.js.map +1 -0
- package/dist/campaign-api.d.ts +151 -0
- package/dist/campaign-api.js +446 -0
- package/dist/campaign-api.js.map +1 -0
- package/dist/campaign-cli.d.ts +6 -0
- package/dist/campaign-cli.js +66 -0
- package/dist/campaign-cli.js.map +1 -0
- package/dist/check-api.d.ts +5 -0
- package/dist/check-api.js +234 -0
- package/dist/check-api.js.map +1 -0
- package/dist/check-budget.d.ts +26 -0
- package/dist/check-budget.js +90 -0
- package/dist/check-budget.js.map +1 -0
- package/dist/check-cli.d.ts +31 -0
- package/dist/check-cli.js +109 -0
- package/dist/check-cli.js.map +1 -0
- package/dist/check-execute.d.ts +29 -0
- package/dist/check-execute.js +420 -0
- package/dist/check-execute.js.map +1 -0
- package/dist/check-plan.d.ts +118 -0
- package/dist/check-plan.js +516 -0
- package/dist/check-plan.js.map +1 -0
- package/dist/check-suite.d.ts +184 -0
- package/dist/check-suite.js +540 -0
- package/dist/check-suite.js.map +1 -0
- package/dist/check-types.d.ts +181 -0
- package/dist/check-types.js +35 -0
- package/dist/check-types.js.map +1 -0
- package/dist/cli-args.d.ts +26 -0
- package/dist/cli-args.js +50 -0
- package/dist/cli-args.js.map +1 -0
- package/dist/cli-output.d.ts +106 -0
- package/dist/cli-output.js +563 -0
- package/dist/cli-output.js.map +1 -0
- package/dist/cli-reference.d.ts +3 -0
- package/dist/cli-reference.js +81 -0
- package/dist/cli-reference.js.map +1 -0
- package/dist/cli-refusal.d.ts +12 -0
- package/dist/cli-refusal.js +52 -0
- package/dist/cli-refusal.js.map +1 -0
- package/dist/cli-shared.d.ts +340 -0
- package/dist/cli-shared.js +537 -0
- package/dist/cli-shared.js.map +1 -0
- package/dist/cli-surface.d.ts +33 -0
- package/dist/cli-surface.js +48 -0
- package/dist/cli-surface.js.map +1 -0
- package/dist/conversation-options.d.ts +15 -0
- package/dist/conversation-options.js +10 -0
- package/dist/conversation-options.js.map +1 -0
- package/dist/credentials-file.d.ts +15 -0
- package/dist/credentials-file.js +56 -0
- package/dist/credentials-file.js.map +1 -0
- package/dist/data-dir.d.ts +1 -1
- package/dist/data-dir.js +1 -1
- package/dist/defect-evidence.d.ts +141 -0
- package/dist/defect-evidence.js +505 -0
- package/dist/defect-evidence.js.map +1 -0
- package/dist/demo-aspect-api.d.ts +209 -0
- package/dist/demo-aspect-api.js +398 -0
- package/dist/demo-aspect-api.js.map +1 -0
- package/dist/demo-aspect-cli.d.ts +3 -0
- package/dist/demo-aspect-cli.js +243 -0
- package/dist/demo-aspect-cli.js.map +1 -0
- package/dist/demo-capture.d.ts +142 -0
- package/dist/demo-capture.js +537 -0
- package/dist/demo-capture.js.map +1 -0
- package/dist/engine.d.ts +35 -0
- package/dist/engine.js +25 -0
- package/dist/engine.js.map +1 -0
- package/dist/environments.d.ts +155 -0
- package/dist/environments.js +364 -0
- package/dist/environments.js.map +1 -0
- package/dist/exit-codes.d.ts +46 -0
- package/dist/exit-codes.js +91 -0
- package/dist/exit-codes.js.map +1 -0
- package/dist/explore-adversarial.d.ts +144 -0
- package/dist/explore-adversarial.js +227 -0
- package/dist/explore-adversarial.js.map +1 -0
- package/dist/explore-api.d.ts +7 -187
- package/dist/explore-api.js +7 -301
- package/dist/explore-api.js.map +1 -1
- package/dist/explore-author.d.ts +79 -0
- package/dist/explore-author.js +86 -0
- package/dist/explore-author.js.map +1 -0
- package/dist/explore-cli.d.ts +7 -0
- package/dist/explore-cli.js +1199 -0
- package/dist/explore-cli.js.map +1 -0
- package/dist/explore-coverage.d.ts +173 -0
- package/dist/explore-coverage.js +246 -0
- package/dist/explore-coverage.js.map +1 -0
- package/dist/explore-feature.d.ts +125 -0
- package/dist/explore-feature.js +218 -0
- package/dist/explore-feature.js.map +1 -0
- package/dist/explore-goal.d.ts +271 -0
- package/dist/explore-goal.js +417 -0
- package/dist/explore-goal.js.map +1 -0
- package/dist/explore-shared.d.ts +177 -0
- package/dist/explore-shared.js +149 -0
- package/dist/explore-shared.js.map +1 -0
- package/dist/explore-specs.d.ts +53 -0
- package/dist/explore-specs.js +333 -0
- package/dist/explore-specs.js.map +1 -0
- package/dist/findings-filing.d.ts +64 -0
- package/dist/findings-filing.js +108 -0
- package/dist/findings-filing.js.map +1 -0
- package/dist/fixture-auth.d.ts +63 -0
- package/dist/fixture-auth.js +95 -0
- package/dist/fixture-auth.js.map +1 -0
- package/dist/fixture-cli.d.ts +99 -0
- package/dist/fixture-cli.js +292 -0
- package/dist/fixture-cli.js.map +1 -0
- package/dist/github-issue-filer.d.ts +62 -0
- package/dist/github-issue-filer.js +189 -0
- package/dist/github-issue-filer.js.map +1 -0
- package/dist/host-health-run.d.ts +25 -0
- package/dist/host-health-run.js +33 -0
- package/dist/host-health-run.js.map +1 -0
- package/dist/http-5xx-outcome.d.ts +6 -0
- package/dist/http-5xx-outcome.js +39 -0
- package/dist/http-5xx-outcome.js.map +1 -0
- package/dist/inbox-cli.d.ts +7 -0
- package/dist/inbox-cli.js +197 -0
- package/dist/inbox-cli.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/init-cli.d.ts +4 -0
- package/dist/init-cli.js +156 -0
- package/dist/init-cli.js.map +1 -0
- package/dist/init-keys.d.ts +30 -2
- package/dist/init-keys.js +19 -5
- package/dist/init-keys.js.map +1 -1
- package/dist/init-next-steps.d.ts +35 -0
- package/dist/init-next-steps.js +79 -0
- package/dist/init-next-steps.js.map +1 -0
- package/dist/invariants-file.d.ts +34 -0
- package/dist/invariants-file.js +68 -0
- package/dist/invariants-file.js.map +1 -0
- package/dist/invariants-validate.d.ts +30 -0
- package/dist/invariants-validate.js +105 -0
- package/dist/invariants-validate.js.map +1 -0
- package/dist/journey-annotate-api.d.ts +76 -0
- package/dist/journey-annotate-api.js +269 -0
- package/dist/journey-annotate-api.js.map +1 -0
- package/dist/journey-annotate-cli.d.ts +9 -0
- package/dist/journey-annotate-cli.js +181 -0
- package/dist/journey-annotate-cli.js.map +1 -0
- package/dist/journey-api.d.ts +104 -2
- package/dist/journey-api.js +163 -20
- package/dist/journey-api.js.map +1 -1
- package/dist/journey-cli.d.ts +4 -0
- package/dist/journey-cli.js +411 -0
- package/dist/journey-cli.js.map +1 -0
- package/dist/journey-demo-api.d.ts +114 -0
- package/dist/journey-demo-api.js +405 -0
- package/dist/journey-demo-api.js.map +1 -0
- package/dist/journey-demo-cli.d.ts +10 -0
- package/dist/journey-demo-cli.js +158 -0
- package/dist/journey-demo-cli.js.map +1 -0
- package/dist/journey-prefix.d.ts +194 -0
- package/dist/journey-prefix.js +279 -0
- package/dist/journey-prefix.js.map +1 -0
- package/dist/key-report.d.ts +49 -0
- package/dist/key-report.js +101 -0
- package/dist/key-report.js.map +1 -0
- package/dist/kill-signal.d.ts +193 -0
- package/dist/kill-signal.js +283 -0
- package/dist/kill-signal.js.map +1 -0
- package/dist/launch-armed.d.ts +37 -0
- package/dist/launch-armed.js +31 -0
- package/dist/launch-armed.js.map +1 -0
- package/dist/ledger-api.d.ts +106 -0
- package/dist/ledger-api.js +239 -0
- package/dist/ledger-api.js.map +1 -0
- package/dist/ledger-cli.d.ts +18 -0
- package/dist/ledger-cli.js +107 -0
- package/dist/ledger-cli.js.map +1 -0
- package/dist/load-api.d.ts +29 -11
- package/dist/load-api.js +23 -15
- package/dist/load-api.js.map +1 -1
- package/dist/load-cli.d.ts +4 -0
- package/dist/load-cli.js +97 -0
- package/dist/load-cli.js.map +1 -0
- package/dist/log-correlation.d.ts +200 -0
- package/dist/log-correlation.js +394 -0
- package/dist/log-correlation.js.map +1 -0
- package/dist/log-lines.d.ts +131 -0
- package/dist/log-lines.js +389 -0
- package/dist/log-lines.js.map +1 -0
- package/dist/log-sources.d.ts +71 -0
- package/dist/log-sources.js +236 -0
- package/dist/log-sources.js.map +1 -0
- package/dist/log-trace.d.ts +80 -0
- package/dist/log-trace.js +210 -0
- package/dist/log-trace.js.map +1 -0
- package/dist/logs-cli.d.ts +3 -0
- package/dist/logs-cli.js +51 -0
- package/dist/logs-cli.js.map +1 -0
- package/dist/logs-retention.d.ts +28 -0
- package/dist/logs-retention.js +85 -0
- package/dist/logs-retention.js.map +1 -0
- package/dist/masked-input.d.ts +22 -0
- package/dist/masked-input.js +99 -0
- package/dist/masked-input.js.map +1 -0
- package/dist/mcp-api.d.ts +91 -1
- package/dist/mcp-api.js +514 -18
- package/dist/mcp-api.js.map +1 -1
- package/dist/mcp-args.d.ts +50 -0
- package/dist/mcp-args.js +164 -0
- package/dist/mcp-args.js.map +1 -0
- package/dist/mcp-cli-bridge.d.ts +34 -0
- package/dist/mcp-cli-bridge.js +62 -0
- package/dist/mcp-cli-bridge.js.map +1 -0
- package/dist/mcp-cli-runner.d.ts +29 -0
- package/dist/mcp-cli-runner.js +44 -0
- package/dist/mcp-cli-runner.js.map +1 -0
- package/dist/mcp-cli-tools.d.ts +129 -0
- package/dist/mcp-cli-tools.js +737 -0
- package/dist/mcp-cli-tools.js.map +1 -0
- package/dist/mcp-paths.d.ts +28 -0
- package/dist/mcp-paths.js +76 -0
- package/dist/mcp-paths.js.map +1 -0
- package/dist/mission-actors.d.ts +40 -0
- package/dist/mission-actors.js +72 -0
- package/dist/mission-actors.js.map +1 -0
- package/dist/mission-api.d.ts +35 -0
- package/dist/mission-api.js +60 -3
- package/dist/mission-api.js.map +1 -1
- package/dist/mission-cli.d.ts +4 -0
- package/dist/mission-cli.js +248 -0
- package/dist/mission-cli.js.map +1 -0
- package/dist/mission-exit.d.ts +22 -0
- package/dist/mission-exit.js +26 -0
- package/dist/mission-exit.js.map +1 -0
- package/dist/mission-fixtures.d.ts +234 -0
- package/dist/mission-fixtures.js +862 -0
- package/dist/mission-fixtures.js.map +1 -0
- package/dist/mission-journal.d.ts +60 -0
- package/dist/mission-journal.js +115 -0
- package/dist/mission-journal.js.map +1 -0
- package/dist/mission-queue-cli.d.ts +10 -0
- package/dist/mission-queue-cli.js +198 -0
- package/dist/mission-queue-cli.js.map +1 -0
- package/dist/mission-queue-runner.d.ts +126 -0
- package/dist/mission-queue-runner.js +406 -0
- package/dist/mission-queue-runner.js.map +1 -0
- package/dist/multi-run-cli.d.ts +37 -0
- package/dist/multi-run-cli.js +116 -0
- package/dist/multi-run-cli.js.map +1 -0
- package/dist/multi-run.d.ts +339 -0
- package/dist/multi-run.js +619 -0
- package/dist/multi-run.js.map +1 -0
- package/dist/openrouter-call.d.ts +22 -0
- package/dist/openrouter-call.js +64 -0
- package/dist/openrouter-call.js.map +1 -0
- package/dist/profile-cli.d.ts +4 -0
- package/dist/profile-cli.js +57 -0
- package/dist/profile-cli.js.map +1 -0
- package/dist/program.d.ts +3 -75
- package/dist/program.js +123 -1921
- package/dist/program.js.map +1 -1
- package/dist/project-dir.d.ts +96 -0
- package/dist/project-dir.js +221 -0
- package/dist/project-dir.js.map +1 -0
- package/dist/record-api.d.ts +13 -7
- package/dist/record-api.js +44 -30
- package/dist/record-api.js.map +1 -1
- package/dist/record-cli.d.ts +4 -0
- package/dist/record-cli.js +76 -0
- package/dist/record-cli.js.map +1 -0
- package/dist/recording-cli.d.ts +4 -0
- package/dist/recording-cli.js +248 -0
- package/dist/recording-cli.js.map +1 -0
- package/dist/regression-api.d.ts +107 -0
- package/dist/regression-api.js +387 -6
- package/dist/regression-api.js.map +1 -1
- package/dist/regression-cli.d.ts +4 -0
- package/dist/regression-cli.js +284 -0
- package/dist/regression-cli.js.map +1 -0
- package/dist/report-api.d.ts +146 -0
- package/dist/report-api.js +431 -0
- package/dist/report-api.js.map +1 -0
- package/dist/report-cli.d.ts +12 -0
- package/dist/report-cli.js +147 -0
- package/dist/report-cli.js.map +1 -0
- package/dist/resource-preflight.d.ts +87 -0
- package/dist/resource-preflight.js +160 -0
- package/dist/resource-preflight.js.map +1 -0
- package/dist/result-schema.d.ts +22 -0
- package/dist/result-schema.js +28 -0
- package/dist/result-schema.js.map +1 -0
- package/dist/run-index.d.ts +29 -0
- package/dist/run-index.js +70 -0
- package/dist/run-index.js.map +1 -0
- package/dist/run-key-preflight.d.ts +42 -0
- package/dist/run-key-preflight.js +82 -0
- package/dist/run-key-preflight.js.map +1 -0
- package/dist/run-screenshots.d.ts +123 -0
- package/dist/run-screenshots.js +235 -0
- package/dist/run-screenshots.js.map +1 -0
- package/dist/runtime-env.d.ts +8 -0
- package/dist/runtime-env.js +23 -0
- package/dist/runtime-env.js.map +1 -0
- package/dist/secret-args.d.ts +23 -0
- package/dist/secret-args.js +45 -0
- package/dist/secret-args.js.map +1 -0
- package/dist/serve-cli.d.ts +4 -0
- package/dist/serve-cli.js +95 -0
- package/dist/serve-cli.js.map +1 -0
- package/dist/server-log-verify.d.ts +36 -0
- package/dist/server-log-verify.js +120 -0
- package/dist/server-log-verify.js.map +1 -0
- package/dist/session-check.d.ts +14 -0
- package/dist/session-check.js +44 -0
- package/dist/session-check.js.map +1 -0
- package/dist/site-cli.d.ts +4 -0
- package/dist/site-cli.js +127 -0
- package/dist/site-cli.js.map +1 -0
- package/dist/site-gate-cli.d.ts +28 -0
- package/dist/site-gate-cli.js +59 -0
- package/dist/site-gate-cli.js.map +1 -0
- package/dist/source-api.js +20 -4
- package/dist/source-api.js.map +1 -1
- package/dist/source-cli.d.ts +4 -0
- package/dist/source-cli.js +284 -0
- package/dist/source-cli.js.map +1 -0
- package/dist/source-run-api.d.ts +27 -1
- package/dist/source-run-api.js +22 -11
- package/dist/source-run-api.js.map +1 -1
- package/dist/storage-state-snapshot.d.ts +50 -0
- package/dist/storage-state-snapshot.js +87 -0
- package/dist/storage-state-snapshot.js.map +1 -0
- package/dist/suite-explore-options.d.ts +321 -0
- package/dist/suite-explore-options.js +162 -0
- package/dist/suite-explore-options.js.map +1 -0
- package/dist/target-config.d.ts +115 -0
- package/dist/target-config.js +225 -0
- package/dist/target-config.js.map +1 -0
- package/dist/transcript-file.d.ts +12 -0
- package/dist/transcript-file.js +23 -0
- package/dist/transcript-file.js.map +1 -0
- package/dist/type-fixture-file.d.ts +9 -0
- package/dist/type-fixture-file.js +48 -0
- package/dist/type-fixture-file.js.map +1 -0
- package/dist/ui-api.d.ts +3 -0
- package/dist/ui-api.js +11 -4
- package/dist/ui-api.js.map +1 -1
- package/dist/usability-capture.d.ts +43 -0
- package/dist/usability-capture.js +327 -0
- package/dist/usability-capture.js.map +1 -0
- package/dist/usage-config.d.ts +36 -0
- package/dist/usage-config.js +114 -0
- package/dist/usage-config.js.map +1 -0
- package/dist/ux-api.d.ts +3 -75
- package/dist/ux-api.js +3 -220
- package/dist/ux-api.js.map +1 -1
- package/dist/ux-claim-probe.d.ts +45 -0
- package/dist/ux-claim-probe.js +386 -0
- package/dist/ux-claim-probe.js.map +1 -0
- package/dist/ux-cli.d.ts +4 -0
- package/dist/ux-cli.js +109 -0
- package/dist/ux-cli.js.map +1 -0
- package/dist/ux-config.d.ts +29 -0
- package/dist/ux-config.js +103 -0
- package/dist/ux-config.js.map +1 -0
- package/dist/ux-evidence.d.ts +54 -0
- package/dist/ux-evidence.js +197 -0
- package/dist/ux-evidence.js.map +1 -0
- package/dist/ux-product.d.ts +14 -0
- package/dist/ux-product.js +39 -0
- package/dist/ux-product.js.map +1 -0
- package/dist/ux-review.d.ts +116 -0
- package/dist/ux-review.js +171 -0
- package/dist/ux-review.js.map +1 -0
- package/dist/ux-usability.d.ts +263 -0
- package/dist/ux-usability.js +580 -0
- package/dist/ux-usability.js.map +1 -0
- package/dist/verify-fix-api.d.ts +194 -0
- package/dist/verify-fix-api.js +506 -0
- package/dist/verify-fix-api.js.map +1 -0
- package/dist/verify-fix-cli.d.ts +4 -0
- package/dist/verify-fix-cli.js +115 -0
- package/dist/verify-fix-cli.js.map +1 -0
- package/package.json +23 -18
- package/skills/jevitate-ci-check/SKILL.md +77 -0
- package/skills/jevitate-demo/SKILL.md +61 -0
- package/skills/jevitate-explore/SKILL.md +96 -87
- package/skills/jevitate-getting-started/SKILL.md +89 -0
- package/skills/jevitate-load-test/SKILL.md +11 -2
- package/skills/jevitate-mission-scope/SKILL.md +19 -11
- package/skills/jevitate-record/SKILL.md +23 -5
- package/skills/jevitate-run-journey/SKILL.md +61 -17
- package/skills/jevitate-sources/SKILL.md +57 -14
- package/skills/jevitate-test-campaign/SKILL.md +203 -0
- package/skills/jevitate-ux-review/SKILL.md +106 -16
- package/skills/jevitate-verify-fix/SKILL.md +76 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createReadStream, statSync } from "node:fs";
|
|
3
|
+
import { clock } from "@jevitate/domain";
|
|
4
|
+
export class LogSourceSpecError extends Error {
|
|
5
|
+
code = "E_LOG_SOURCE_SPEC";
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "LogSourceSpecError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/** Parses one `--log-source` value. Throws `LogSourceSpecError` on an unrecognized/empty spec. */
|
|
12
|
+
export function parseLogSourceSpec(raw) {
|
|
13
|
+
const i = raw.indexOf(":");
|
|
14
|
+
if (i < 0)
|
|
15
|
+
throw new LogSourceSpecError(`--log-source must be file:<path>, docker:<container> or cmd:<command>: got ${JSON.stringify(raw)}`);
|
|
16
|
+
const kind = raw.slice(0, i);
|
|
17
|
+
const rest = raw.slice(i + 1);
|
|
18
|
+
if (rest.trim() === "")
|
|
19
|
+
throw new LogSourceSpecError(`--log-source ${kind}: missing value`);
|
|
20
|
+
if (kind === "file")
|
|
21
|
+
return { kind: "file", path: rest, raw };
|
|
22
|
+
if (kind === "docker")
|
|
23
|
+
return { kind: "docker", container: rest, raw };
|
|
24
|
+
if (kind === "cmd")
|
|
25
|
+
return { kind: "cmd", command: rest, raw };
|
|
26
|
+
throw new LogSourceSpecError(`--log-source: unknown source kind ${JSON.stringify(kind)} (want file:, docker: or cmd:)`);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Validates every spec up front, INCLUDING the `cmd:` + `--allow-log-cmd` gate — so a run refuses
|
|
30
|
+
* before any browser opens, the same fail-closed discipline as `--invariants`.
|
|
31
|
+
*/
|
|
32
|
+
export function parseLogSourceSpecs(raw, allowLogCmd) {
|
|
33
|
+
const specs = raw.map(parseLogSourceSpec);
|
|
34
|
+
for (const s of specs) {
|
|
35
|
+
if (s.kind === "cmd" && !allowLogCmd) {
|
|
36
|
+
throw new LogSourceSpecError(`--log-source ${s.raw} needs --allow-log-cmd (an operator-declared command is run as a subprocess)`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return specs;
|
|
40
|
+
}
|
|
41
|
+
const DEFAULT_MAX_LINES = 20_000;
|
|
42
|
+
const DEFAULT_FILE_POLL_MS = 250;
|
|
43
|
+
function lineSplitter(onLine) {
|
|
44
|
+
let carry = "";
|
|
45
|
+
return (chunk) => {
|
|
46
|
+
carry += chunk.toString("utf8");
|
|
47
|
+
const lines = carry.split(/\r?\n/);
|
|
48
|
+
carry = lines.pop() ?? "";
|
|
49
|
+
for (const line of lines)
|
|
50
|
+
if (line !== "")
|
|
51
|
+
onLine(line, clock.now());
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function boundedOnLine(handle, opts) {
|
|
55
|
+
const max = opts.maxLines ?? DEFAULT_MAX_LINES;
|
|
56
|
+
return (raw, epochMs) => {
|
|
57
|
+
if (handle.linesRead >= max) {
|
|
58
|
+
handle.truncated = true;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
handle.linesRead += 1;
|
|
62
|
+
opts.onLine(raw, epochMs);
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* `file:` — tails from the CURRENT end by polling the file's size (no `fs.watch`: flaky across
|
|
67
|
+
* filesystems, notably over WSL's `/mnt/c` drvfs — AGENTS.md). Handles truncation/rotation, AND a
|
|
68
|
+
* file that does not exist yet at open time (a backend that has not logged anything until later in
|
|
69
|
+
* the run is the common case, not an error): if the file already exists, its EXISTING content is
|
|
70
|
+
* history and skipped (`position` starts at its current size); if it does not exist yet, nothing
|
|
71
|
+
* can predate this run, so `position` starts at 0 and the first line written after the source
|
|
72
|
+
* opened is the first one delivered, however long the file takes to appear.
|
|
73
|
+
*/
|
|
74
|
+
function openFileSource(spec, opts) {
|
|
75
|
+
let closed = false;
|
|
76
|
+
let timer;
|
|
77
|
+
let position = 0;
|
|
78
|
+
const handle = {
|
|
79
|
+
spec,
|
|
80
|
+
opened: false,
|
|
81
|
+
linesRead: 0,
|
|
82
|
+
truncated: false,
|
|
83
|
+
close: async () => {
|
|
84
|
+
closed = true;
|
|
85
|
+
if (timer !== undefined)
|
|
86
|
+
clock.clearInterval(timer);
|
|
87
|
+
if (!handle.opened)
|
|
88
|
+
handle.error = `${spec.path}: never appeared during the run`;
|
|
89
|
+
},
|
|
90
|
+
killSync: () => {
|
|
91
|
+
closed = true;
|
|
92
|
+
if (timer !== undefined)
|
|
93
|
+
clock.clearInterval(timer);
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
const deliver = lineSplitter(boundedOnLine(handle, opts));
|
|
97
|
+
try {
|
|
98
|
+
// The file already exists: its content predates this source and is skipped (never replayed).
|
|
99
|
+
position = statSync(spec.path).size;
|
|
100
|
+
handle.opened = true;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Does not exist yet: `position` stays 0, so whatever is written once it appears is ALL new.
|
|
104
|
+
}
|
|
105
|
+
const poll = () => {
|
|
106
|
+
if (closed)
|
|
107
|
+
return;
|
|
108
|
+
let size;
|
|
109
|
+
try {
|
|
110
|
+
size = statSync(spec.path).size;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return; // still not there, or a transient stat failure — the next poll tries again
|
|
114
|
+
}
|
|
115
|
+
handle.opened = true; // confirmed readable now, even if it appeared after the initial check
|
|
116
|
+
if (size <= position) {
|
|
117
|
+
if (size < position)
|
|
118
|
+
position = 0; // truncated/rotated: resume from the new start
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const stream = createReadStream(spec.path, { start: position, end: size - 1 });
|
|
122
|
+
stream.on("data", (chunk) => deliver(chunk));
|
|
123
|
+
stream.on("error", () => undefined);
|
|
124
|
+
position = size;
|
|
125
|
+
};
|
|
126
|
+
timer = clock.setInterval(poll, opts.filePollMs ?? DEFAULT_FILE_POLL_MS);
|
|
127
|
+
timer.unref?.();
|
|
128
|
+
return handle;
|
|
129
|
+
}
|
|
130
|
+
/** Kills a spawned child's whole process GROUP (POSIX): `docker logs`/`cmd:`'s own children die
|
|
131
|
+
* too, not just the immediate process. Falls back to killing just the child if the group send fails. */
|
|
132
|
+
function killGroup(child) {
|
|
133
|
+
const pid = child.pid;
|
|
134
|
+
if (pid === undefined)
|
|
135
|
+
return;
|
|
136
|
+
try {
|
|
137
|
+
process.kill(-pid, "SIGTERM");
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
try {
|
|
141
|
+
child.kill("SIGTERM");
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// Already gone.
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function openProcessSource(spec, command, args, opts, useShell) {
|
|
149
|
+
let child;
|
|
150
|
+
// #199: set the moment WE ask the child to stop (mission end) — BEFORE `killGroup` sends the
|
|
151
|
+
// signal, so the (always-async) `exit` event below, whenever it lands, can tell "we stopped this"
|
|
152
|
+
// from "it died on its own". A source jevitate stopped itself is a normal end of tailing, not a
|
|
153
|
+
// failure, no matter what exit code the child reports for its own signal handling (a `docker logs
|
|
154
|
+
// -f`/`cmd:` child commonly self-exits 143 for SIGTERM rather than dying to the raw signal).
|
|
155
|
+
let stopRequested = false;
|
|
156
|
+
const handle = {
|
|
157
|
+
spec,
|
|
158
|
+
opened: false,
|
|
159
|
+
linesRead: 0,
|
|
160
|
+
truncated: false,
|
|
161
|
+
close: async () => {
|
|
162
|
+
stopRequested = true;
|
|
163
|
+
if (child === undefined)
|
|
164
|
+
return;
|
|
165
|
+
killGroup(child);
|
|
166
|
+
await new Promise((resolve) => {
|
|
167
|
+
if (child === undefined || child.exitCode !== null || child.signalCode !== null) {
|
|
168
|
+
resolve();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const done = () => resolve();
|
|
172
|
+
child.once("exit", done);
|
|
173
|
+
const t = clock.setTimeout(done, 2000);
|
|
174
|
+
t.unref?.();
|
|
175
|
+
});
|
|
176
|
+
},
|
|
177
|
+
killSync: () => {
|
|
178
|
+
stopRequested = true;
|
|
179
|
+
if (child !== undefined)
|
|
180
|
+
killGroup(child);
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
try {
|
|
184
|
+
child = spawn(command, args, { detached: true, shell: useShell, stdio: ["ignore", "pipe", "pipe"] });
|
|
185
|
+
}
|
|
186
|
+
catch (e) {
|
|
187
|
+
handle.error = `cannot start ${spec.raw}: ${e instanceof Error ? e.message : String(e)}`;
|
|
188
|
+
return handle;
|
|
189
|
+
}
|
|
190
|
+
handle.opened = true;
|
|
191
|
+
const deliver = lineSplitter(boundedOnLine(handle, opts));
|
|
192
|
+
child.stdout.on("data", deliver);
|
|
193
|
+
child.stderr.on("data", deliver); // most backends interleave logs on stderr too (docker logs does)
|
|
194
|
+
child.on("error", (e) => {
|
|
195
|
+
handle.error = `${spec.raw}: ${e.message}`;
|
|
196
|
+
});
|
|
197
|
+
// Startup health check (#169): `docker logs -f <container>` on a container that isn't running (or
|
|
198
|
+
// doesn't exist) exits almost immediately with a non-zero code — its stderr ("Error: No such
|
|
199
|
+
// container: …") would otherwise just look like an ordinary, if odd, log line and count toward
|
|
200
|
+
// `linesRead`, hiding the failure. An early non-zero exit is decisive regardless of what was piped
|
|
201
|
+
// through in the meantime: this source was never actually tailing the backend.
|
|
202
|
+
//
|
|
203
|
+
// #199: BUT only when the child died on its own. `close()`/`killSync()` set `stopRequested` before
|
|
204
|
+
// signaling, so an exit that lands after WE asked this source to stop (mission end) is a normal
|
|
205
|
+
// end of tailing — never a failure, whatever code/signal the child reports for handling its own
|
|
206
|
+
// termination (SIGTERM commonly comes back as a self-reported exit code like 143, not `signal`).
|
|
207
|
+
child.on("exit", (code, signal) => {
|
|
208
|
+
if (stopRequested)
|
|
209
|
+
return;
|
|
210
|
+
if (code !== null && code !== 0 && handle.error === undefined) {
|
|
211
|
+
handle.error = `${spec.raw}: exited with code ${code}${signal === null ? "" : ` (signal ${signal})`} — the source may not be running`;
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
child.unref();
|
|
215
|
+
return handle;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Opens one already-validated `--log-source`. Never throws: an unreadable/unspawnable source comes
|
|
219
|
+
* back with `.error` set rather than aborting a run that has other sources (the caller decides
|
|
220
|
+
* whether that's fatal — a `--log-defect` oracle must not count an unreadable source as "held").
|
|
221
|
+
*/
|
|
222
|
+
export function openLogSource(spec, opts) {
|
|
223
|
+
if (spec.kind === "file")
|
|
224
|
+
return openFileSource(spec, opts);
|
|
225
|
+
if (spec.kind === "docker")
|
|
226
|
+
return openProcessSource(spec, "docker", ["logs", "-f", "--since", "0s", spec.container], opts, false);
|
|
227
|
+
return openProcessSource(spec, spec.command, [], opts, true);
|
|
228
|
+
}
|
|
229
|
+
export function openLogSources(specs, opts) {
|
|
230
|
+
return specs.map((spec) => openLogSource(spec, opts));
|
|
231
|
+
}
|
|
232
|
+
/** Closes every handle, best-effort, in parallel. Never throws. */
|
|
233
|
+
export async function closeLogSources(handles) {
|
|
234
|
+
await Promise.all(handles.map((h) => h.close().catch(() => undefined)));
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=log-sources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-sources.js","sourceRoot":"","sources":["../src/log-sources.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request ↔ backend-log correlation by TRACE / CORRELATION ID (#204), instead of by time window.
|
|
3
|
+
*
|
|
4
|
+
* The browser side (`RequestIdLedger`) records, for every request the run's page sends, the ids it
|
|
5
|
+
* carries: W3C `traceparent` (its trace-id), `x-request-id`, `x-correlation-id`, `request-id`,
|
|
6
|
+
* `x-amzn-trace-id` (its `Root`), `x-b3-traceid`, `x-cloud-trace-context` (its trace) — on the
|
|
7
|
+
* REQUEST (a frontend with OpenTelemetry) or the RESPONSE (a server-generated request id) — plus
|
|
8
|
+
* any operator-named header (`--log-correlation-header`). The log side (`lineIds`) finds those
|
|
9
|
+
* ids in a backend log line: any whitespace/punctuation-delimited token equal to a recorded id
|
|
10
|
+
* (exact — never a prefix or a substring of a longer token).
|
|
11
|
+
*
|
|
12
|
+
* `ServerLogRuntime` then attaches a line that carries a run's id to EXACTLY that request (method,
|
|
13
|
+
* URL, status) and to the step that sent it — whatever time the line landed — and falls back to the
|
|
14
|
+
* time window only for lines with no id. Once ids demonstrably correlate (at least one line matched
|
|
15
|
+
* a run's id), a line carrying a DIFFERENT id (`foreignIds`: a `trace_id=`/`request_id=` key, a
|
|
16
|
+
* `traceparent`, or an operator `--log-id-pattern`) belongs to other work — another user, a
|
|
17
|
+
* background job — and is never attributed to this run.
|
|
18
|
+
*
|
|
19
|
+
* Ids are not secrets, but every URL is redacted and every attached line is redacted with the run's
|
|
20
|
+
* secrets, as before.
|
|
21
|
+
*/
|
|
22
|
+
/** Headers whose value identifies a request across the browser and the backend (lower-case). */
|
|
23
|
+
export declare const DEFAULT_CORRELATION_HEADERS: readonly string[];
|
|
24
|
+
/** The correlation id(s) a header value carries. */
|
|
25
|
+
export declare function idsFromHeader(name: string, value: string): string[];
|
|
26
|
+
/** One request the run's page sent, with the ids that identify it to the backend. */
|
|
27
|
+
export interface CorrelatedRequest {
|
|
28
|
+
readonly method: string;
|
|
29
|
+
/** Redacted URL (the shared `redactUrl` rule). */
|
|
30
|
+
readonly url: string;
|
|
31
|
+
/** The response status; null while (or if never) answered. */
|
|
32
|
+
status: number | null;
|
|
33
|
+
/** When it was sent (epoch ms). */
|
|
34
|
+
readonly startedAtMs: number;
|
|
35
|
+
readonly ids: string[];
|
|
36
|
+
}
|
|
37
|
+
/** The page surface the ledger listens on (Playwright's `Page`, structurally). */
|
|
38
|
+
export interface RequestEvents {
|
|
39
|
+
on(event: "request", fn: (r: LedgerRequest) => void): unknown;
|
|
40
|
+
on(event: "response", fn: (r: {
|
|
41
|
+
request(): LedgerRequest;
|
|
42
|
+
status(): number;
|
|
43
|
+
headers(): Record<string, string>;
|
|
44
|
+
}) => void): unknown;
|
|
45
|
+
}
|
|
46
|
+
export interface LedgerRequest {
|
|
47
|
+
url(): string;
|
|
48
|
+
method(): string;
|
|
49
|
+
headers(): Record<string, string>;
|
|
50
|
+
}
|
|
51
|
+
/** Validates `--log-correlation-header` names (fails closed on the first bad one). */
|
|
52
|
+
export declare function parseCorrelationHeaders(raw: readonly string[]): string[];
|
|
53
|
+
/**
|
|
54
|
+
* Parses `--log-id-pattern` values: `/regex/flags` whose first capture group (or whole match) is a
|
|
55
|
+
* correlation id in a log line of the operator's own format. Bounded and compiled once.
|
|
56
|
+
*/
|
|
57
|
+
export declare function parseLogIdPatterns(raw: readonly string[]): RegExp[];
|
|
58
|
+
/** Records every request a run's page sends with its correlation ids (#204). */
|
|
59
|
+
export declare class RequestIdLedger {
|
|
60
|
+
#private;
|
|
61
|
+
constructor(opts?: {
|
|
62
|
+
readonly headers?: readonly string[];
|
|
63
|
+
readonly now?: () => number;
|
|
64
|
+
});
|
|
65
|
+
/** Starts listening on a page (call before its first navigation). */
|
|
66
|
+
observe(page: RequestEvents): void;
|
|
67
|
+
/** How many requests carried at least one id. */
|
|
68
|
+
get requestsWithIds(): number;
|
|
69
|
+
/** The request a log line belongs to (by an id it carries), or undefined. */
|
|
70
|
+
requestFor(raw: string): {
|
|
71
|
+
readonly request: CorrelatedRequest;
|
|
72
|
+
readonly id: string;
|
|
73
|
+
} | undefined;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The correlation ids a log line DECLARES (#204): a keyed id (`trace_id=`, `"requestId":`, …), a
|
|
77
|
+
* `traceparent`, or an operator `--log-id-pattern` match. Used to tell another request's line
|
|
78
|
+
* (foreign) from one with no id at all (time-window fallback).
|
|
79
|
+
*/
|
|
80
|
+
export declare function declaredIds(raw: string, patterns?: readonly RegExp[]): string[];
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { redactUrl } from "@jevitate/ai-core";
|
|
2
|
+
import { LogSpecError } from "./log-lines.js";
|
|
3
|
+
import { clock } from "@jevitate/domain";
|
|
4
|
+
/**
|
|
5
|
+
* Request ↔ backend-log correlation by TRACE / CORRELATION ID (#204), instead of by time window.
|
|
6
|
+
*
|
|
7
|
+
* The browser side (`RequestIdLedger`) records, for every request the run's page sends, the ids it
|
|
8
|
+
* carries: W3C `traceparent` (its trace-id), `x-request-id`, `x-correlation-id`, `request-id`,
|
|
9
|
+
* `x-amzn-trace-id` (its `Root`), `x-b3-traceid`, `x-cloud-trace-context` (its trace) — on the
|
|
10
|
+
* REQUEST (a frontend with OpenTelemetry) or the RESPONSE (a server-generated request id) — plus
|
|
11
|
+
* any operator-named header (`--log-correlation-header`). The log side (`lineIds`) finds those
|
|
12
|
+
* ids in a backend log line: any whitespace/punctuation-delimited token equal to a recorded id
|
|
13
|
+
* (exact — never a prefix or a substring of a longer token).
|
|
14
|
+
*
|
|
15
|
+
* `ServerLogRuntime` then attaches a line that carries a run's id to EXACTLY that request (method,
|
|
16
|
+
* URL, status) and to the step that sent it — whatever time the line landed — and falls back to the
|
|
17
|
+
* time window only for lines with no id. Once ids demonstrably correlate (at least one line matched
|
|
18
|
+
* a run's id), a line carrying a DIFFERENT id (`foreignIds`: a `trace_id=`/`request_id=` key, a
|
|
19
|
+
* `traceparent`, or an operator `--log-id-pattern`) belongs to other work — another user, a
|
|
20
|
+
* background job — and is never attributed to this run.
|
|
21
|
+
*
|
|
22
|
+
* Ids are not secrets, but every URL is redacted and every attached line is redacted with the run's
|
|
23
|
+
* secrets, as before.
|
|
24
|
+
*/
|
|
25
|
+
/** Headers whose value identifies a request across the browser and the backend (lower-case). */
|
|
26
|
+
export const DEFAULT_CORRELATION_HEADERS = [
|
|
27
|
+
"traceparent",
|
|
28
|
+
"x-request-id",
|
|
29
|
+
"x-correlation-id",
|
|
30
|
+
"request-id",
|
|
31
|
+
"x-amzn-trace-id",
|
|
32
|
+
"x-b3-traceid",
|
|
33
|
+
"x-cloud-trace-context",
|
|
34
|
+
];
|
|
35
|
+
/** An id shorter than this would match unrelated tokens (a "1", a "200"): never used. */
|
|
36
|
+
const MIN_ID_CHARS = 8;
|
|
37
|
+
const MAX_ID_CHARS = 128;
|
|
38
|
+
const ID_SHAPE = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/;
|
|
39
|
+
const TRACEPARENT = /^[0-9a-f]{2}-([0-9a-f]{32})-[0-9a-f]{16}-[0-9a-f]{2}$/i;
|
|
40
|
+
const HEADER_NAME = /^[A-Za-z0-9!#$%&'*+.^_`|~-]{1,64}$/;
|
|
41
|
+
/** Most requests the ledger keeps (the oldest are dropped past it). */
|
|
42
|
+
const MAX_REQUESTS = 5_000;
|
|
43
|
+
/** Normalizes an id for comparison (hex trace ids are case-insensitive in practice). */
|
|
44
|
+
const norm = (id) => id.toLowerCase();
|
|
45
|
+
function usable(id) {
|
|
46
|
+
const v = id.trim();
|
|
47
|
+
if (v.length < MIN_ID_CHARS || v.length > MAX_ID_CHARS || !ID_SHAPE.test(v))
|
|
48
|
+
return null;
|
|
49
|
+
// An all-zero trace id is the W3C "invalid" value: it identifies nothing.
|
|
50
|
+
if (/^0+$/.test(v.replace(/[-:.]/g, "")))
|
|
51
|
+
return null;
|
|
52
|
+
return v;
|
|
53
|
+
}
|
|
54
|
+
/** The correlation id(s) a header value carries. */
|
|
55
|
+
export function idsFromHeader(name, value) {
|
|
56
|
+
const n = name.toLowerCase();
|
|
57
|
+
const v = value.trim();
|
|
58
|
+
let candidates;
|
|
59
|
+
if (n === "traceparent") {
|
|
60
|
+
const m = TRACEPARENT.exec(v);
|
|
61
|
+
candidates = m === null ? [] : [m[1]];
|
|
62
|
+
}
|
|
63
|
+
else if (n === "x-amzn-trace-id") {
|
|
64
|
+
const root = /(?:^|;)\s*Root=([^;]+)/i.exec(v);
|
|
65
|
+
candidates = root === null ? [] : [root[1]];
|
|
66
|
+
}
|
|
67
|
+
else if (n === "x-cloud-trace-context") {
|
|
68
|
+
candidates = [v.split("/")[0] ?? ""];
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
candidates = v.split(",").map((s) => s.trim());
|
|
72
|
+
}
|
|
73
|
+
return candidates.map(usable).filter((x) => x !== null);
|
|
74
|
+
}
|
|
75
|
+
/** Validates `--log-correlation-header` names (fails closed on the first bad one). */
|
|
76
|
+
export function parseCorrelationHeaders(raw) {
|
|
77
|
+
return raw.map((h) => {
|
|
78
|
+
const v = h.trim().toLowerCase();
|
|
79
|
+
if (!HEADER_NAME.test(v))
|
|
80
|
+
throw new LogSpecError(`--log-correlation-header: not a header name: ${JSON.stringify(h)}`);
|
|
81
|
+
return v;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Parses `--log-id-pattern` values: `/regex/flags` whose first capture group (or whole match) is a
|
|
86
|
+
* correlation id in a log line of the operator's own format. Bounded and compiled once.
|
|
87
|
+
*/
|
|
88
|
+
export function parseLogIdPatterns(raw) {
|
|
89
|
+
return raw.map((p) => {
|
|
90
|
+
const m = /^\/(.+)\/([a-z]*)$/s.exec(p);
|
|
91
|
+
if (m === null)
|
|
92
|
+
throw new LogSpecError(`--log-id-pattern must be /regex/flags, got ${JSON.stringify(p)}`);
|
|
93
|
+
if (m[1].length > 500)
|
|
94
|
+
throw new LogSpecError("--log-id-pattern is too long (max 500 chars)");
|
|
95
|
+
try {
|
|
96
|
+
const flags = m[2].replace(/[gy]/g, "");
|
|
97
|
+
return new RegExp(m[1], `${flags}g`);
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
throw new LogSpecError(`--log-id-pattern: invalid regex ${JSON.stringify(p)}: ${e instanceof Error ? e.message : String(e)}`);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/** Records every request a run's page sends with its correlation ids (#204). */
|
|
105
|
+
export class RequestIdLedger {
|
|
106
|
+
#headers;
|
|
107
|
+
#now;
|
|
108
|
+
#byRequest = new WeakMap();
|
|
109
|
+
#byId = new Map();
|
|
110
|
+
#requests = [];
|
|
111
|
+
constructor(opts = {}) {
|
|
112
|
+
this.#headers = new Set([...DEFAULT_CORRELATION_HEADERS, ...(opts.headers ?? []).map((h) => h.toLowerCase())]);
|
|
113
|
+
this.#now = opts.now ?? clock.now;
|
|
114
|
+
}
|
|
115
|
+
/** Starts listening on a page (call before its first navigation). */
|
|
116
|
+
observe(page) {
|
|
117
|
+
page.on("request", (r) => {
|
|
118
|
+
let rec;
|
|
119
|
+
let headers;
|
|
120
|
+
try {
|
|
121
|
+
rec = { method: r.method().toUpperCase(), url: redactUrl(r.url()), status: null, startedAtMs: this.#now(), ids: [] };
|
|
122
|
+
headers = r.headers();
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return; // a request whose page is already gone: nothing to correlate
|
|
126
|
+
}
|
|
127
|
+
this.#byRequest.set(r, rec);
|
|
128
|
+
this.#requests.push(rec);
|
|
129
|
+
if (this.#requests.length > MAX_REQUESTS) {
|
|
130
|
+
const old = this.#requests.shift();
|
|
131
|
+
for (const id of old?.ids ?? [])
|
|
132
|
+
if (this.#byId.get(norm(id)) === old)
|
|
133
|
+
this.#byId.delete(norm(id));
|
|
134
|
+
}
|
|
135
|
+
this.#addIds(rec, headers);
|
|
136
|
+
});
|
|
137
|
+
page.on("response", (res) => {
|
|
138
|
+
try {
|
|
139
|
+
const rec = this.#byRequest.get(res.request());
|
|
140
|
+
if (rec === undefined)
|
|
141
|
+
return;
|
|
142
|
+
rec.status = res.status();
|
|
143
|
+
this.#addIds(rec, res.headers());
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// a response whose page is already gone: nothing more to learn
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
#addIds(rec, headers) {
|
|
151
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
152
|
+
if (!this.#headers.has(name.toLowerCase()))
|
|
153
|
+
continue;
|
|
154
|
+
for (const id of idsFromHeader(name, value)) {
|
|
155
|
+
if (rec.ids.includes(id))
|
|
156
|
+
continue;
|
|
157
|
+
rec.ids.push(id);
|
|
158
|
+
// The same id on two requests (a trace spanning several calls): the first sender owns it.
|
|
159
|
+
if (!this.#byId.has(norm(id)))
|
|
160
|
+
this.#byId.set(norm(id), rec);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
/** How many requests carried at least one id. */
|
|
165
|
+
get requestsWithIds() {
|
|
166
|
+
return this.#requests.filter((r) => r.ids.length > 0).length;
|
|
167
|
+
}
|
|
168
|
+
/** The request a log line belongs to (by an id it carries), or undefined. */
|
|
169
|
+
requestFor(raw) {
|
|
170
|
+
if (this.#byId.size === 0)
|
|
171
|
+
return undefined;
|
|
172
|
+
for (const token of tokens(raw)) {
|
|
173
|
+
const hit = this.#byId.get(norm(token));
|
|
174
|
+
if (hit !== undefined)
|
|
175
|
+
return { request: hit, id: token };
|
|
176
|
+
const tp = TRACEPARENT.exec(token);
|
|
177
|
+
if (tp !== null) {
|
|
178
|
+
const t = this.#byId.get(norm(tp[1]));
|
|
179
|
+
if (t !== undefined)
|
|
180
|
+
return { request: t, id: tp[1] };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** The tokens of a log line an id can be (delimited by anything an id cannot contain). */
|
|
187
|
+
function tokens(raw) {
|
|
188
|
+
return raw.split(/[^A-Za-z0-9._:-]+/).filter((t) => t.length >= MIN_ID_CHARS && t.length <= 200);
|
|
189
|
+
}
|
|
190
|
+
/** Built-in id keys: `trace_id=…`, `"traceId": "…"`, `request-id: …`, `correlationId=…`, `x-request-id=…`. */
|
|
191
|
+
const KEYED_ID = /(?:^|[^A-Za-z0-9])(?:x-)?(?:trace|request|req|correlation|corr)[_.-]?id["']?\s*[:=]\s*["']?([A-Za-z0-9][A-Za-z0-9._:-]{7,127})/gi;
|
|
192
|
+
const TRACEPARENT_IN_TEXT = /\b[0-9a-f]{2}-([0-9a-f]{32})-[0-9a-f]{16}-[0-9a-f]{2}\b/gi;
|
|
193
|
+
/**
|
|
194
|
+
* The correlation ids a log line DECLARES (#204): a keyed id (`trace_id=`, `"requestId":`, …), a
|
|
195
|
+
* `traceparent`, or an operator `--log-id-pattern` match. Used to tell another request's line
|
|
196
|
+
* (foreign) from one with no id at all (time-window fallback).
|
|
197
|
+
*/
|
|
198
|
+
export function declaredIds(raw, patterns = []) {
|
|
199
|
+
const out = new Set();
|
|
200
|
+
for (const re of [KEYED_ID, TRACEPARENT_IN_TEXT, ...patterns]) {
|
|
201
|
+
re.lastIndex = 0;
|
|
202
|
+
for (const m of raw.matchAll(re)) {
|
|
203
|
+
const id = usable(m[1] ?? m[0]);
|
|
204
|
+
if (id !== null)
|
|
205
|
+
out.add(id);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return [...out];
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=log-trace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-trace.js","sourceRoot":"","sources":["../src/log-trace.ts"],"names":[],"mappings":"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"}
|
package/dist/logs-cli.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ok, fail } from "./envelope.js";
|
|
2
|
+
import { LogsConfigError, loadLogsRetention, pruneLogs } from "./logs-retention.js";
|
|
3
|
+
import { logsRoot } from "./project-dir.js";
|
|
4
|
+
import { resolveDataDir } from "./data-dir.js";
|
|
5
|
+
import { emitJson } from "./cli-shared.js";
|
|
6
|
+
/** The commands that write run output under `.jevitate/logs` — pruned before, when auto-prune is on. */
|
|
7
|
+
const LOG_WRITING_COMMANDS = new Set(["explore", "explore-author-journey", "record", "ux", "check", "run", "verify-fix", "capture"]);
|
|
8
|
+
export function registerLogsCommands(program, deps) {
|
|
9
|
+
const configPath = deps.logs?.configPath ?? resolveDataDir(["config.json"]);
|
|
10
|
+
if (deps.logs?.autoPrune === true) {
|
|
11
|
+
program.hook("preAction", (_root, action) => {
|
|
12
|
+
if (!LOG_WRITING_COMMANDS.has(action.name()))
|
|
13
|
+
return;
|
|
14
|
+
// A malformed retention config fails closed (the command is refused); a file that cannot be
|
|
15
|
+
// deleted is housekeeping, not the run's business: it is reported and the command goes on.
|
|
16
|
+
const retention = loadLogsRetention(configPath);
|
|
17
|
+
try {
|
|
18
|
+
pruneLogs(deps.logs?.logsRoot ?? logsRoot(), retention);
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
process.stderr.write(`warning: log pruning skipped: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
const logs = program.command("logs").description("run output under .jevitate/logs (dated; pruned by retention)");
|
|
26
|
+
logs
|
|
27
|
+
.command("prune")
|
|
28
|
+
.description("delete runs older than the retention TTL, always keeping the newest runs (config.json logs.ttlDays / logs.keepLatest; defaults 14 and 50)")
|
|
29
|
+
.option("--dir <dir>", "logs root to prune (default: the project's .jevitate/logs, else ~/.jevitate/logs)")
|
|
30
|
+
.option("--dry-run", "list what would be deleted, deleting nothing")
|
|
31
|
+
.option("--json", "emit a JSON envelope")
|
|
32
|
+
.action(function () {
|
|
33
|
+
const o = this.opts();
|
|
34
|
+
try {
|
|
35
|
+
const retention = loadLogsRetention(configPath);
|
|
36
|
+
const report = pruneLogs(o.dir ?? deps.logs?.logsRoot ?? logsRoot(), retention, { ...(o.dryRun === true ? { dryRun: true } : {}) });
|
|
37
|
+
const data = { ...report, retention, dryRun: o.dryRun === true };
|
|
38
|
+
if (o.json === true)
|
|
39
|
+
emitJson(program, ok(data));
|
|
40
|
+
else
|
|
41
|
+
program.configureOutput().writeOut?.(`${o.dryRun === true ? "would remove" : "removed"} ${report.removed.length} run(s); kept ${report.keptRuns} (ttl ${retention.ttlDays}d, keep latest ${retention.keepLatest})\n`);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
if (err instanceof LogsConfigError)
|
|
45
|
+
emitJson(program, fail(err.code, err.message));
|
|
46
|
+
else
|
|
47
|
+
emitJson(program, fail("E_LOGS_PRUNE", err instanceof Error ? err.message : String(err)));
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=logs-cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logs-cli.js","sourceRoot":"","sources":["../src/logs-cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retention for run output under `.jevitate/logs/<date>/` (0.2.0): a run older than `ttlDays` is
|
|
3
|
+
* deleted, but the newest `keepLatest` runs are always kept, so a quiet project is never wiped.
|
|
4
|
+
* A run is every file and folder sharing its artifact stem (`explore-2026-09-25T01-26-29-787Z.*`).
|
|
5
|
+
* Pruned at the start of each command that writes logs, or on demand with `jevitate logs prune`.
|
|
6
|
+
* Configured in `~/.jevitate/config.json`: `{ "logs": { "ttlDays": 14, "keepLatest": 50 } }`.
|
|
7
|
+
*/
|
|
8
|
+
export interface LogsRetention {
|
|
9
|
+
readonly ttlDays: number;
|
|
10
|
+
readonly keepLatest: number;
|
|
11
|
+
}
|
|
12
|
+
export declare const DEFAULT_LOGS_RETENTION: LogsRetention;
|
|
13
|
+
export declare class LogsConfigError extends Error {
|
|
14
|
+
readonly code: "E_LOGS_CONFIG";
|
|
15
|
+
}
|
|
16
|
+
/** The `logs` section of the config file; the defaults when the file or section is absent. A malformed value fails closed. */
|
|
17
|
+
export declare function loadLogsRetention(path?: string): LogsRetention;
|
|
18
|
+
export interface PruneReport {
|
|
19
|
+
readonly root: string;
|
|
20
|
+
/** Runs deleted (`<date>/<stem>`). */
|
|
21
|
+
readonly removed: string[];
|
|
22
|
+
readonly keptRuns: number;
|
|
23
|
+
}
|
|
24
|
+
/** Applies `retention` to the logs under `root`. `dryRun` reports what would go, deleting nothing. */
|
|
25
|
+
export declare function pruneLogs(root: string, retention: LogsRetention, opts?: {
|
|
26
|
+
readonly nowMs?: number;
|
|
27
|
+
readonly dryRun?: boolean;
|
|
28
|
+
}): PruneReport;
|