@codyswann/lisa 2.235.0 → 2.237.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/dist/cli/check-learnings-budget-cmd.d.ts +20 -0
- package/dist/cli/check-learnings-budget-cmd.d.ts.map +1 -0
- package/dist/cli/check-learnings-budget-cmd.js +59 -0
- package/dist/cli/check-learnings-budget-cmd.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +29 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/ui-automations-adapters.d.ts +50 -0
- package/dist/cli/ui-automations-adapters.d.ts.map +1 -0
- package/dist/cli/ui-automations-adapters.js +162 -0
- package/dist/cli/ui-automations-adapters.js.map +1 -0
- package/dist/cli/ui-automations.d.ts +89 -0
- package/dist/cli/ui-automations.d.ts.map +1 -0
- package/dist/cli/ui-automations.js +148 -0
- package/dist/cli/ui-automations.js.map +1 -0
- package/dist/cli/ui-cmd.d.ts +5 -0
- package/dist/cli/ui-cmd.d.ts.map +1 -1
- package/dist/cli/ui-cmd.js +17 -0
- package/dist/cli/ui-cmd.js.map +1 -1
- package/dist/cli/ui-deploy-pipeline-model.d.ts +70 -0
- package/dist/cli/ui-deploy-pipeline-model.d.ts.map +1 -0
- package/dist/cli/ui-deploy-pipeline-model.js +127 -0
- package/dist/cli/ui-deploy-pipeline-model.js.map +1 -0
- package/dist/cli/ui-deploy-pipeline.d.ts +25 -0
- package/dist/cli/ui-deploy-pipeline.d.ts.map +1 -0
- package/dist/cli/ui-deploy-pipeline.js +192 -0
- package/dist/cli/ui-deploy-pipeline.js.map +1 -0
- package/dist/cli/ui-detected-stacks.d.ts +20 -0
- package/dist/cli/ui-detected-stacks.d.ts.map +1 -0
- package/dist/cli/ui-detected-stacks.js +28 -0
- package/dist/cli/ui-detected-stacks.js.map +1 -0
- package/dist/cli/ui-enabled-plugins.d.ts +52 -0
- package/dist/cli/ui-enabled-plugins.d.ts.map +1 -0
- package/dist/cli/ui-enabled-plugins.js +253 -0
- package/dist/cli/ui-enabled-plugins.js.map +1 -0
- package/dist/cli/ui-github-repo-expected.d.ts +36 -0
- package/dist/cli/ui-github-repo-expected.d.ts.map +1 -0
- package/dist/cli/ui-github-repo-expected.js +118 -0
- package/dist/cli/ui-github-repo-expected.js.map +1 -0
- package/dist/cli/ui-github-repo-gh.d.ts +36 -0
- package/dist/cli/ui-github-repo-gh.d.ts.map +1 -0
- package/dist/cli/ui-github-repo-gh.js +112 -0
- package/dist/cli/ui-github-repo-gh.js.map +1 -0
- package/dist/cli/ui-github-repo-map.d.ts +25 -0
- package/dist/cli/ui-github-repo-map.d.ts.map +1 -0
- package/dist/cli/ui-github-repo-map.js +150 -0
- package/dist/cli/ui-github-repo-map.js.map +1 -0
- package/dist/cli/ui-github-repo.d.ts +51 -0
- package/dist/cli/ui-github-repo.d.ts.map +1 -0
- package/dist/cli/ui-github-repo.js +117 -0
- package/dist/cli/ui-github-repo.js.map +1 -0
- package/dist/cli/ui-observability-checks.d.ts +23 -0
- package/dist/cli/ui-observability-checks.d.ts.map +1 -0
- package/dist/cli/ui-observability-checks.js +156 -0
- package/dist/cli/ui-observability-checks.js.map +1 -0
- package/dist/cli/ui-observability-providers.d.ts +69 -0
- package/dist/cli/ui-observability-providers.d.ts.map +1 -0
- package/dist/cli/ui-observability-providers.js +99 -0
- package/dist/cli/ui-observability-providers.js.map +1 -0
- package/dist/core/learnings-budget-check.d.ts +42 -0
- package/dist/core/learnings-budget-check.d.ts.map +1 -0
- package/dist/core/learnings-budget-check.js +210 -0
- package/dist/core/learnings-budget-check.js.map +1 -0
- package/dist/core/learnings-document.js +6 -1
- package/dist/core/learnings-document.js.map +1 -1
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/scripts/check-learnings-budget.ts +29 -163
- package/ui/README.md +5 -3
- package/ui/index.html +1097 -432
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AwsCountCheck, SentryReachabilityCheck } from "./ui-observability-providers.js";
|
|
2
|
+
/**
|
|
3
|
+
* Default Sentry reachability: REST when SENTRY_AUTH_TOKEN is set.
|
|
4
|
+
* @param timeoutMs - Request budget
|
|
5
|
+
* @param signal - Abort signal
|
|
6
|
+
* @returns Reachability outcome
|
|
7
|
+
*/
|
|
8
|
+
export declare const defaultSentryCheck: SentryReachabilityCheck;
|
|
9
|
+
/**
|
|
10
|
+
* Count CloudWatch alarms after confirming AWS identity.
|
|
11
|
+
* @param timeoutMs - Command budget
|
|
12
|
+
* @param signal - Abort signal
|
|
13
|
+
* @returns Alarm count, or not-authenticated
|
|
14
|
+
*/
|
|
15
|
+
export declare const defaultCloudWatchCheck: AwsCountCheck;
|
|
16
|
+
/**
|
|
17
|
+
* Count recent X-Ray traces after confirming AWS identity.
|
|
18
|
+
* @param timeoutMs - Command budget
|
|
19
|
+
* @param signal - Abort signal
|
|
20
|
+
* @returns Trace count, or not-authenticated
|
|
21
|
+
*/
|
|
22
|
+
export declare const defaultXRayCheck: AwsCountCheck;
|
|
23
|
+
//# sourceMappingURL=ui-observability-checks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-observability-checks.d.ts","sourceRoot":"","sources":["../../src/cli/ui-observability-checks.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,aAAa,EACb,uBAAuB,EACxB,MAAM,iCAAiC,CAAC;AAiGzC;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,EAAE,uBAsBhC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,EAAE,aAyBpC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,aA0B9B,CAAC"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shell-out reachability checks for observability provider probes.
|
|
3
|
+
*
|
|
4
|
+
* Reuses the same CLIs/tokens skills already use (SENTRY_AUTH_TOKEN REST,
|
|
5
|
+
* `aws sts` / `cloudwatch` / `xray`) — no second SDK.
|
|
6
|
+
* @module cli/ui-observability-checks
|
|
7
|
+
*/
|
|
8
|
+
import { execFile } from "node:child_process";
|
|
9
|
+
const NOT_AUTHENTICATED = "not-authenticated";
|
|
10
|
+
/**
|
|
11
|
+
* Read one env var through a single reviewable process.env exception.
|
|
12
|
+
* @param name - Environment variable name
|
|
13
|
+
* @returns Trimmed value, or undefined when unset/blank
|
|
14
|
+
*/
|
|
15
|
+
function readEnv(name) {
|
|
16
|
+
// eslint-disable-next-line no-restricted-syntax -- observability probe must read developer-local auth tokens once
|
|
17
|
+
const value = process.env[name];
|
|
18
|
+
if (typeof value !== "string") {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
const trimmed = value.trim();
|
|
22
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Run a fixed argv via execFile without a shell.
|
|
26
|
+
* @param file - Executable name
|
|
27
|
+
* @param args - Argument vector
|
|
28
|
+
* @param timeoutMs - Child-process timeout
|
|
29
|
+
* @param signal - Probe cancellation signal
|
|
30
|
+
* @returns stdout on success
|
|
31
|
+
*/
|
|
32
|
+
async function runCommand(file, args, timeoutMs, signal) {
|
|
33
|
+
return await new Promise((resolve, reject) => {
|
|
34
|
+
execFile(
|
|
35
|
+
// eslint-disable-next-line sonarjs/no-os-command-from-path -- fixed user-installed aws CLI
|
|
36
|
+
file, [...args], { signal, timeout: timeoutMs, encoding: "utf8" }, (error, stdout) => {
|
|
37
|
+
if (error !== null) {
|
|
38
|
+
reject(error);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
resolve(stdout);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Classify an AWS CLI failure as missing auth vs other probe failure.
|
|
47
|
+
* @param error - Thrown command error
|
|
48
|
+
* @returns Whether the failure indicates missing credentials
|
|
49
|
+
*/
|
|
50
|
+
function isAwsAuthFailure(error) {
|
|
51
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
52
|
+
const lowered = message.toLowerCase();
|
|
53
|
+
return (lowered.includes("unable to locate credentials") ||
|
|
54
|
+
lowered.includes("could not load credentials") ||
|
|
55
|
+
lowered.includes("no credentials") ||
|
|
56
|
+
lowered.includes("expiredtoken") ||
|
|
57
|
+
lowered.includes("the security token included in the request is invalid") ||
|
|
58
|
+
lowered.includes("is not authorized to perform") ||
|
|
59
|
+
lowered.includes("accessdenied") ||
|
|
60
|
+
lowered.includes("error loading sso") ||
|
|
61
|
+
lowered.includes("sso session"));
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Confirm AWS identity, mapping credential failures to not-authenticated.
|
|
65
|
+
* @param timeoutMs - Command budget
|
|
66
|
+
* @param signal - Abort signal
|
|
67
|
+
* @returns not-authenticated when credentials are missing; otherwise void
|
|
68
|
+
*/
|
|
69
|
+
async function requireAwsIdentity(timeoutMs, signal) {
|
|
70
|
+
try {
|
|
71
|
+
await runCommand("aws", ["sts", "get-caller-identity", "--output", "json"], timeoutMs, signal);
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (isAwsAuthFailure(error)) {
|
|
76
|
+
return NOT_AUTHENTICATED;
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Default Sentry reachability: REST when SENTRY_AUTH_TOKEN is set.
|
|
83
|
+
* @param timeoutMs - Request budget
|
|
84
|
+
* @param signal - Abort signal
|
|
85
|
+
* @returns Reachability outcome
|
|
86
|
+
*/
|
|
87
|
+
export const defaultSentryCheck = async (timeoutMs, signal) => {
|
|
88
|
+
const token = readEnv("SENTRY_AUTH_TOKEN");
|
|
89
|
+
if (token === undefined) {
|
|
90
|
+
return NOT_AUTHENTICATED;
|
|
91
|
+
}
|
|
92
|
+
const response = await fetch("https://sentry.io/api/0/", {
|
|
93
|
+
headers: {
|
|
94
|
+
accept: "application/json",
|
|
95
|
+
authorization: `Bearer ${token}`,
|
|
96
|
+
},
|
|
97
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)]),
|
|
98
|
+
});
|
|
99
|
+
if (response.ok) {
|
|
100
|
+
return "reachable";
|
|
101
|
+
}
|
|
102
|
+
if (response.status === 401 || response.status === 403) {
|
|
103
|
+
return NOT_AUTHENTICATED;
|
|
104
|
+
}
|
|
105
|
+
throw new Error(`Sentry API returned HTTP ${String(response.status)}`);
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Count CloudWatch alarms after confirming AWS identity.
|
|
109
|
+
* @param timeoutMs - Command budget
|
|
110
|
+
* @param signal - Abort signal
|
|
111
|
+
* @returns Alarm count, or not-authenticated
|
|
112
|
+
*/
|
|
113
|
+
export const defaultCloudWatchCheck = async (timeoutMs, signal) => {
|
|
114
|
+
const auth = await requireAwsIdentity(timeoutMs, signal);
|
|
115
|
+
if (auth !== undefined) {
|
|
116
|
+
return auth;
|
|
117
|
+
}
|
|
118
|
+
const stdout = await runCommand("aws", ["cloudwatch", "describe-alarms", "--output", "json"], timeoutMs, signal);
|
|
119
|
+
const parsed = JSON.parse(stdout);
|
|
120
|
+
const metric = Array.isArray(parsed.MetricAlarms)
|
|
121
|
+
? parsed.MetricAlarms.length
|
|
122
|
+
: 0;
|
|
123
|
+
const composite = Array.isArray(parsed.CompositeAlarms)
|
|
124
|
+
? parsed.CompositeAlarms.length
|
|
125
|
+
: 0;
|
|
126
|
+
return metric + composite;
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* Count recent X-Ray traces after confirming AWS identity.
|
|
130
|
+
* @param timeoutMs - Command budget
|
|
131
|
+
* @param signal - Abort signal
|
|
132
|
+
* @returns Trace count, or not-authenticated
|
|
133
|
+
*/
|
|
134
|
+
export const defaultXRayCheck = async (timeoutMs, signal) => {
|
|
135
|
+
const auth = await requireAwsIdentity(timeoutMs, signal);
|
|
136
|
+
if (auth !== undefined) {
|
|
137
|
+
return auth;
|
|
138
|
+
}
|
|
139
|
+
const end = new Date();
|
|
140
|
+
const start = new Date(end.getTime() - 60 * 60 * 1000);
|
|
141
|
+
const stdout = await runCommand("aws", [
|
|
142
|
+
"xray",
|
|
143
|
+
"get-trace-summaries",
|
|
144
|
+
"--start-time",
|
|
145
|
+
start.toISOString(),
|
|
146
|
+
"--end-time",
|
|
147
|
+
end.toISOString(),
|
|
148
|
+
"--output",
|
|
149
|
+
"json",
|
|
150
|
+
], timeoutMs, signal);
|
|
151
|
+
const parsed = JSON.parse(stdout);
|
|
152
|
+
return Array.isArray(parsed.TraceSummaries)
|
|
153
|
+
? parsed.TraceSummaries.length
|
|
154
|
+
: 0;
|
|
155
|
+
};
|
|
156
|
+
//# sourceMappingURL=ui-observability-checks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-observability-checks.js","sourceRoot":"","sources":["../../src/cli/ui-observability-checks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live-status probes for connected observability providers.
|
|
3
|
+
*
|
|
4
|
+
* READ-ONLY detection by reachability (Sentry auth, CloudWatch alarms, X-Ray
|
|
5
|
+
* traces). Missing credentials degrade to unknown — never a fabricated
|
|
6
|
+
* "disconnected" claim.
|
|
7
|
+
* @module cli/ui-observability-providers
|
|
8
|
+
*/
|
|
9
|
+
import type { JsonValue } from "../sync/json-path.js";
|
|
10
|
+
import type { ProbeResult, StatusProbe } from "./ui-status.js";
|
|
11
|
+
/** Structured value for a reachable observability provider. */
|
|
12
|
+
export type ObservabilityProviderValue = {
|
|
13
|
+
readonly [key: string]: JsonValue;
|
|
14
|
+
readonly status: "connected" | "connected-but-empty";
|
|
15
|
+
readonly emptyKind?: "alarms" | "traces";
|
|
16
|
+
};
|
|
17
|
+
/** Injectable Sentry reachability check. */
|
|
18
|
+
export type SentryReachabilityCheck = (timeoutMs: number, signal: AbortSignal) => Promise<"reachable" | "not-authenticated">;
|
|
19
|
+
/** Injectable AWS count check (alarms or traces), or not-authenticated. */
|
|
20
|
+
export type AwsCountCheck = (timeoutMs: number, signal: AbortSignal) => Promise<number | "not-authenticated">;
|
|
21
|
+
export declare const SENTRY_PROBE_ID = "sentry";
|
|
22
|
+
export declare const CLOUDWATCH_ALARMS_PROBE_ID = "cloudwatch-alarms";
|
|
23
|
+
export declare const XRAY_PROBE_ID = "x-ray";
|
|
24
|
+
/**
|
|
25
|
+
* Map Sentry reachability onto the live-status tri-state.
|
|
26
|
+
* @param outcome - Reachability check result
|
|
27
|
+
* @returns Probe result for the sentry provider
|
|
28
|
+
*/
|
|
29
|
+
export declare function mapSentryReachability(outcome: "reachable" | "not-authenticated"): ProbeResult<ObservabilityProviderValue>;
|
|
30
|
+
/**
|
|
31
|
+
* Map an AWS count check onto connected / empty / unknown.
|
|
32
|
+
* @param outcome - Count or not-authenticated
|
|
33
|
+
* @param emptyKind - Whether zero means no alarms or no traces
|
|
34
|
+
* @returns Probe result for CloudWatch or X-Ray
|
|
35
|
+
*/
|
|
36
|
+
export declare function mapAwsCountCheck(outcome: number | "not-authenticated", emptyKind: "alarms" | "traces"): ProbeResult<ObservabilityProviderValue>;
|
|
37
|
+
/**
|
|
38
|
+
* Create the Sentry observability probe.
|
|
39
|
+
* @param options - Injectable collaborators
|
|
40
|
+
* @param options.check - Optional reachability check for focused tests
|
|
41
|
+
* @returns Bounded status probe
|
|
42
|
+
*/
|
|
43
|
+
export declare function createSentryProbe(options?: {
|
|
44
|
+
readonly check?: SentryReachabilityCheck;
|
|
45
|
+
}): StatusProbe<ObservabilityProviderValue>;
|
|
46
|
+
/**
|
|
47
|
+
* Create the CloudWatch Alarms observability probe.
|
|
48
|
+
* @param options - Injectable collaborators
|
|
49
|
+
* @param options.check - Optional alarm-count check for focused tests
|
|
50
|
+
* @returns Bounded status probe
|
|
51
|
+
*/
|
|
52
|
+
export declare function createCloudWatchAlarmsProbe(options?: {
|
|
53
|
+
readonly check?: AwsCountCheck;
|
|
54
|
+
}): StatusProbe<ObservabilityProviderValue>;
|
|
55
|
+
/**
|
|
56
|
+
* Create the X-Ray observability probe.
|
|
57
|
+
* @param options - Injectable collaborators
|
|
58
|
+
* @param options.check - Optional trace-count check for focused tests
|
|
59
|
+
* @returns Bounded status probe
|
|
60
|
+
*/
|
|
61
|
+
export declare function createXRayProbe(options?: {
|
|
62
|
+
readonly check?: AwsCountCheck;
|
|
63
|
+
}): StatusProbe<ObservabilityProviderValue>;
|
|
64
|
+
/**
|
|
65
|
+
* Register all three observability provider probes.
|
|
66
|
+
* @returns Sentry, CloudWatch Alarms, and X-Ray probes
|
|
67
|
+
*/
|
|
68
|
+
export declare function createObservabilityProviderProbes(): readonly StatusProbe<ObservabilityProviderValue>[];
|
|
69
|
+
//# sourceMappingURL=ui-observability-providers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-observability-providers.d.ts","sourceRoot":"","sources":["../../src/cli/ui-observability-providers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAO/D,+DAA+D;AAC/D,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,qBAAqB,CAAC;IACrD,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC;CAC1C,CAAC;AAEF,4CAA4C;AAC5C,MAAM,MAAM,uBAAuB,GAAG,CACpC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,WAAW,KAChB,OAAO,CAAC,WAAW,GAAG,mBAAmB,CAAC,CAAC;AAEhD,2EAA2E;AAC3E,MAAM,MAAM,aAAa,GAAG,CAC1B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,WAAW,KAChB,OAAO,CAAC,MAAM,GAAG,mBAAmB,CAAC,CAAC;AAE3C,eAAO,MAAM,eAAe,WAAW,CAAC;AACxC,eAAO,MAAM,0BAA0B,sBAAsB,CAAC;AAC9D,eAAO,MAAM,aAAa,UAAU,CAAC;AASrC;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,WAAW,GAAG,mBAAmB,GACzC,WAAW,CAAC,0BAA0B,CAAC,CASzC;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,GAAG,mBAAmB,EACrC,SAAS,EAAE,QAAQ,GAAG,QAAQ,GAC7B,WAAW,CAAC,0BAA0B,CAAC,CAezC;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,GAAE;IACP,QAAQ,CAAC,KAAK,CAAC,EAAE,uBAAuB,CAAC;CACrC,GACL,WAAW,CAAC,0BAA0B,CAAC,CAQzC;AAED;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,GAAE;IACP,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;CAC3B,GACL,WAAW,CAAC,0BAA0B,CAAC,CAQzC;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,OAAO,GAAE;IACP,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;CAC3B,GACL,WAAW,CAAC,0BAA0B,CAAC,CAQzC;AAED;;;GAGG;AACH,wBAAgB,iCAAiC,IAAI,SAAS,WAAW,CAAC,0BAA0B,CAAC,EAAE,CAMtG"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { defaultCloudWatchCheck, defaultSentryCheck, defaultXRayCheck, } from "./ui-observability-checks.js";
|
|
2
|
+
export const SENTRY_PROBE_ID = "sentry";
|
|
3
|
+
export const CLOUDWATCH_ALARMS_PROBE_ID = "cloudwatch-alarms";
|
|
4
|
+
export const XRAY_PROBE_ID = "x-ray";
|
|
5
|
+
const PROBE_TIMEOUT_MS = 5_000;
|
|
6
|
+
const NOT_AUTHENTICATED = "not-authenticated";
|
|
7
|
+
const SENTRY_UNKNOWN_MESSAGE = "Sentry auth/MCP is not reachable — authenticate Sentry MCP/CLI or set SENTRY_AUTH_TOKEN";
|
|
8
|
+
const AWS_UNKNOWN_MESSAGE = "AWS credentials are not authenticated for this machine";
|
|
9
|
+
/**
|
|
10
|
+
* Map Sentry reachability onto the live-status tri-state.
|
|
11
|
+
* @param outcome - Reachability check result
|
|
12
|
+
* @returns Probe result for the sentry provider
|
|
13
|
+
*/
|
|
14
|
+
export function mapSentryReachability(outcome) {
|
|
15
|
+
if (outcome === "reachable") {
|
|
16
|
+
return { state: "value", value: { status: "connected" } };
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
state: "unknown",
|
|
20
|
+
reason: NOT_AUTHENTICATED,
|
|
21
|
+
message: SENTRY_UNKNOWN_MESSAGE,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Map an AWS count check onto connected / empty / unknown.
|
|
26
|
+
* @param outcome - Count or not-authenticated
|
|
27
|
+
* @param emptyKind - Whether zero means no alarms or no traces
|
|
28
|
+
* @returns Probe result for CloudWatch or X-Ray
|
|
29
|
+
*/
|
|
30
|
+
export function mapAwsCountCheck(outcome, emptyKind) {
|
|
31
|
+
if (outcome === "not-authenticated") {
|
|
32
|
+
return {
|
|
33
|
+
state: "unknown",
|
|
34
|
+
reason: NOT_AUTHENTICATED,
|
|
35
|
+
message: AWS_UNKNOWN_MESSAGE,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (outcome > 0) {
|
|
39
|
+
return { state: "value", value: { status: "connected" } };
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
state: "value",
|
|
43
|
+
value: { status: "connected-but-empty", emptyKind },
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Create the Sentry observability probe.
|
|
48
|
+
* @param options - Injectable collaborators
|
|
49
|
+
* @param options.check - Optional reachability check for focused tests
|
|
50
|
+
* @returns Bounded status probe
|
|
51
|
+
*/
|
|
52
|
+
export function createSentryProbe(options = {}) {
|
|
53
|
+
const check = options.check ?? defaultSentryCheck;
|
|
54
|
+
return {
|
|
55
|
+
id: SENTRY_PROBE_ID,
|
|
56
|
+
timeoutMs: PROBE_TIMEOUT_MS,
|
|
57
|
+
run: async (signal) => mapSentryReachability(await check(PROBE_TIMEOUT_MS + 250, signal)),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Create the CloudWatch Alarms observability probe.
|
|
62
|
+
* @param options - Injectable collaborators
|
|
63
|
+
* @param options.check - Optional alarm-count check for focused tests
|
|
64
|
+
* @returns Bounded status probe
|
|
65
|
+
*/
|
|
66
|
+
export function createCloudWatchAlarmsProbe(options = {}) {
|
|
67
|
+
const check = options.check ?? defaultCloudWatchCheck;
|
|
68
|
+
return {
|
|
69
|
+
id: CLOUDWATCH_ALARMS_PROBE_ID,
|
|
70
|
+
timeoutMs: PROBE_TIMEOUT_MS,
|
|
71
|
+
run: async (signal) => mapAwsCountCheck(await check(PROBE_TIMEOUT_MS + 250, signal), "alarms"),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Create the X-Ray observability probe.
|
|
76
|
+
* @param options - Injectable collaborators
|
|
77
|
+
* @param options.check - Optional trace-count check for focused tests
|
|
78
|
+
* @returns Bounded status probe
|
|
79
|
+
*/
|
|
80
|
+
export function createXRayProbe(options = {}) {
|
|
81
|
+
const check = options.check ?? defaultXRayCheck;
|
|
82
|
+
return {
|
|
83
|
+
id: XRAY_PROBE_ID,
|
|
84
|
+
timeoutMs: PROBE_TIMEOUT_MS,
|
|
85
|
+
run: async (signal) => mapAwsCountCheck(await check(PROBE_TIMEOUT_MS + 250, signal), "traces"),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Register all three observability provider probes.
|
|
90
|
+
* @returns Sentry, CloudWatch Alarms, and X-Ray probes
|
|
91
|
+
*/
|
|
92
|
+
export function createObservabilityProviderProbes() {
|
|
93
|
+
return [
|
|
94
|
+
createSentryProbe(),
|
|
95
|
+
createCloudWatchAlarmsProbe(),
|
|
96
|
+
createXRayProbe(),
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=ui-observability-providers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-observability-providers.js","sourceRoot":"","sources":["../../src/cli/ui-observability-providers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** A learnings file that satisfies every hard budget. */
|
|
2
|
+
export interface LearningsBudgetOk {
|
|
3
|
+
readonly kind: "ok";
|
|
4
|
+
readonly entryCount: number;
|
|
5
|
+
readonly maxEntries: number;
|
|
6
|
+
readonly measuredTokens: number;
|
|
7
|
+
readonly maxTokens: number;
|
|
8
|
+
}
|
|
9
|
+
/** No learnings file exists at the resolved path — an expected, silent case. */
|
|
10
|
+
export interface LearningsBudgetMissing {
|
|
11
|
+
readonly kind: "missing";
|
|
12
|
+
readonly detail: string;
|
|
13
|
+
}
|
|
14
|
+
/** The learnings file breached a budget or is otherwise unsafe or invalid. */
|
|
15
|
+
export interface LearningsBudgetViolation {
|
|
16
|
+
readonly kind: "violation";
|
|
17
|
+
readonly detail: string;
|
|
18
|
+
}
|
|
19
|
+
/** Structured outcome of one budget check. */
|
|
20
|
+
export type LearningsBudgetResult = LearningsBudgetOk | LearningsBudgetMissing | LearningsBudgetViolation;
|
|
21
|
+
/**
|
|
22
|
+
* Check one already-resolved absolute learnings path against the shared hard
|
|
23
|
+
* budgets. Callers resolve the path (from config or an explicit argument) and
|
|
24
|
+
* decide the exit policy from the returned discriminated union.
|
|
25
|
+
* @param file - Absolute learnings file path
|
|
26
|
+
* @returns Structured budget-check result
|
|
27
|
+
*/
|
|
28
|
+
export declare function checkLearningsBudget(file: string): Promise<LearningsBudgetResult>;
|
|
29
|
+
/**
|
|
30
|
+
* Render a caught failure without allowing filesystem paths or control bytes
|
|
31
|
+
* embedded in an Error message to forge additional terminal/CI output.
|
|
32
|
+
* @param error - Unknown thrown failure
|
|
33
|
+
* @returns Stable, single-line diagnostic detail
|
|
34
|
+
*/
|
|
35
|
+
export declare function formatErrorDetail(error: unknown): string;
|
|
36
|
+
/**
|
|
37
|
+
* Quote one path after applying terminal-safe JSON-style escaping.
|
|
38
|
+
* @param file - Path to render in diagnostics
|
|
39
|
+
* @returns Quoted, terminal-safe path
|
|
40
|
+
*/
|
|
41
|
+
export declare function formatDiagnosticPath(file: string): string;
|
|
42
|
+
//# sourceMappingURL=learnings-budget-check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings-budget-check.d.ts","sourceRoot":"","sources":["../../src/core/learnings-budget-check.ts"],"names":[],"mappings":"AA4BA,yDAAyD;AACzD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,gFAAgF;AAChF,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,8EAA8E;AAC9E,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,8CAA8C;AAC9C,MAAM,MAAM,qBAAqB,GAC7B,iBAAiB,GACjB,sBAAsB,GACtB,wBAAwB,CAAC;AAE7B;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,qBAAqB,CAAC,CAgChC;AA2CD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAYxD;AAoCD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzD"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reusable core check for the canonical project-learnings document and its hard
|
|
3
|
+
* budgets.
|
|
4
|
+
*
|
|
5
|
+
* Extracted from the package-facing `scripts/check-learnings-budget.ts` so the
|
|
6
|
+
* identical bounded-read, token-budget, per-entry validation, and canonical
|
|
7
|
+
* format checks can be shared by every caller — the existing package script and
|
|
8
|
+
* the `lisa check-learnings-budget` CLI subcommand that ships the gate into host
|
|
9
|
+
* projects' CI. Every hardening property of the original script is preserved
|
|
10
|
+
* here: a bounded regular-file read, an `O_NONBLOCK` open with TOCTOU stat
|
|
11
|
+
* checks, and terminal-escape-safe diagnostic text. The function never throws
|
|
12
|
+
* for an expected condition — a missing file is a distinct result and every
|
|
13
|
+
* other failure is a `violation` carrying a single-line, terminal-safe detail —
|
|
14
|
+
* so each caller owns its own exit policy.
|
|
15
|
+
* @module learnings-budget-check
|
|
16
|
+
*/
|
|
17
|
+
import { constants } from "node:fs";
|
|
18
|
+
import { open } from "node:fs/promises";
|
|
19
|
+
import { LEARNINGS_CONTRACT, estimateLearningTokens, } from "./learnings-contract.js";
|
|
20
|
+
import { parseLearningsFile, renderLearningsFile, } from "./learnings-document.js";
|
|
21
|
+
import { validateLearningEntry } from "./learnings-entry.js";
|
|
22
|
+
/**
|
|
23
|
+
* Check one already-resolved absolute learnings path against the shared hard
|
|
24
|
+
* budgets. Callers resolve the path (from config or an explicit argument) and
|
|
25
|
+
* decide the exit policy from the returned discriminated union.
|
|
26
|
+
* @param file - Absolute learnings file path
|
|
27
|
+
* @returns Structured budget-check result
|
|
28
|
+
*/
|
|
29
|
+
export async function checkLearningsBudget(file) {
|
|
30
|
+
try {
|
|
31
|
+
const content = await readBoundedRegularFile(file, LEARNINGS_CONTRACT.maxTokens);
|
|
32
|
+
const measuredTokens = estimateLearningTokens(content);
|
|
33
|
+
if (measuredTokens > LEARNINGS_CONTRACT.maxTokens) {
|
|
34
|
+
throw new Error(`maxTokens exceeded: measured ${measuredTokens}, allowed ${LEARNINGS_CONTRACT.maxTokens}`);
|
|
35
|
+
}
|
|
36
|
+
const entries = parseLearningsFile(content);
|
|
37
|
+
for (const entry of entries) {
|
|
38
|
+
validateLearningEntry(entry);
|
|
39
|
+
}
|
|
40
|
+
if (renderLearningsFile(entries) !== content) {
|
|
41
|
+
throw new Error("non-canonical project learnings format");
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
kind: "ok",
|
|
45
|
+
entryCount: entries.length,
|
|
46
|
+
maxEntries: LEARNINGS_CONTRACT.maxEntries,
|
|
47
|
+
measuredTokens,
|
|
48
|
+
maxTokens: LEARNINGS_CONTRACT.maxTokens,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
const detail = formatErrorDetail(error);
|
|
53
|
+
return isFileNotFound(error)
|
|
54
|
+
? { kind: "missing", detail }
|
|
55
|
+
: { kind: "violation", detail: withRemediation(detail, file) };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Append a terse, actionable remediation clause to a file-level budget breach
|
|
60
|
+
* so an operator reading CI output learns the fix, not just the number. Only
|
|
61
|
+
* the whole-file budgets (token ceiling, entry count, canonical format) are
|
|
62
|
+
* augmented; per-entry validation failures already name the offending entry
|
|
63
|
+
* and are left verbatim, as are non-budget filesystem errors.
|
|
64
|
+
* @param detail - Terminal-safe diagnostic detail
|
|
65
|
+
* @param file - Absolute learnings file path
|
|
66
|
+
* @returns The detail, with a remediation clause when one applies
|
|
67
|
+
*/
|
|
68
|
+
function withRemediation(detail, file) {
|
|
69
|
+
if (detail.startsWith("Invalid learning entry")) {
|
|
70
|
+
return detail;
|
|
71
|
+
}
|
|
72
|
+
const target = formatDiagnosticPath(file);
|
|
73
|
+
if (detail.includes("maxEntries")) {
|
|
74
|
+
return `${detail} — consolidate or remove entries in ${target} to fit the learnings budget`;
|
|
75
|
+
}
|
|
76
|
+
if (detail.includes("maxTokens")) {
|
|
77
|
+
return `${detail} — shorten or remove entries in ${target} to fit the learnings budget`;
|
|
78
|
+
}
|
|
79
|
+
if (detail.includes("canonical") || detail.includes("format")) {
|
|
80
|
+
return `${detail} — re-generate ${target} with the learnings writer to restore the canonical format`;
|
|
81
|
+
}
|
|
82
|
+
return detail;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Whether a caught failure is a "file does not exist" filesystem error, which
|
|
86
|
+
* callers treat as an expected, silent condition rather than a violation.
|
|
87
|
+
* @param error - Unknown thrown failure
|
|
88
|
+
* @returns True when the error reports an absent path
|
|
89
|
+
*/
|
|
90
|
+
function isFileNotFound(error) {
|
|
91
|
+
return (error !== null &&
|
|
92
|
+
typeof error === "object" &&
|
|
93
|
+
readOwnString(error, "code") === "ENOENT");
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Render a caught failure without allowing filesystem paths or control bytes
|
|
97
|
+
* embedded in an Error message to forge additional terminal/CI output.
|
|
98
|
+
* @param error - Unknown thrown failure
|
|
99
|
+
* @returns Stable, single-line diagnostic detail
|
|
100
|
+
*/
|
|
101
|
+
export function formatErrorDetail(error) {
|
|
102
|
+
if (error !== null && typeof error === "object") {
|
|
103
|
+
const code = readOwnString(error, "code");
|
|
104
|
+
if (code !== undefined && /^[A-Z][A-Z0-9_]*$/u.test(code)) {
|
|
105
|
+
const syscall = readOwnString(error, "syscall");
|
|
106
|
+
return syscall === undefined
|
|
107
|
+
? `filesystem error ${code}`
|
|
108
|
+
: `filesystem error ${code} during ${escapeDiagnosticText(syscall)}`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
112
|
+
return escapeDiagnosticText(message);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Read one inert own string property without invoking an accessor.
|
|
116
|
+
* @param candidate - Object to inspect
|
|
117
|
+
* @param key - Property name to read
|
|
118
|
+
* @returns The own string value, or undefined
|
|
119
|
+
*/
|
|
120
|
+
function readOwnString(candidate, key) {
|
|
121
|
+
const descriptor = Object.getOwnPropertyDescriptor(candidate, key);
|
|
122
|
+
return descriptor !== undefined &&
|
|
123
|
+
"value" in descriptor &&
|
|
124
|
+
typeof descriptor.value === "string"
|
|
125
|
+
? descriptor.value
|
|
126
|
+
: undefined;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Escape terminal controls while retaining ordinary diagnostic wording.
|
|
130
|
+
* @param value - Raw diagnostic text
|
|
131
|
+
* @returns Terminal-safe, single-line text
|
|
132
|
+
*/
|
|
133
|
+
function escapeDiagnosticText(value) {
|
|
134
|
+
const jsonBody = JSON.stringify(value).slice(1, -1);
|
|
135
|
+
return Array.from(jsonBody)
|
|
136
|
+
.map(character => {
|
|
137
|
+
const code = character.charCodeAt(0);
|
|
138
|
+
const isForbidden = (code >= 0x7f && code <= 0x9f) || code === 0x2028 || code === 0x2029;
|
|
139
|
+
return isForbidden
|
|
140
|
+
? `\\u${code.toString(16).padStart(4, "0")}`
|
|
141
|
+
: character;
|
|
142
|
+
})
|
|
143
|
+
.join("");
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Quote one path after applying terminal-safe JSON-style escaping.
|
|
147
|
+
* @param file - Path to render in diagnostics
|
|
148
|
+
* @returns Quoted, terminal-safe path
|
|
149
|
+
*/
|
|
150
|
+
export function formatDiagnosticPath(file) {
|
|
151
|
+
return `"${escapeDiagnosticText(file)}"`;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Read at most one byte beyond the hard budget from one verified regular-file
|
|
155
|
+
* handle. Non-blocking open prevents a FIFO path from stalling the gate.
|
|
156
|
+
* @param file - Absolute candidate learnings path
|
|
157
|
+
* @param maximumBytes - Shared executable maxTokens byte ceiling
|
|
158
|
+
* @returns Strictly decoded UTF-8 content within the byte ceiling
|
|
159
|
+
*/
|
|
160
|
+
async function readBoundedRegularFile(file, maximumBytes) {
|
|
161
|
+
const handle = await open(file, constants.O_RDONLY | constants.O_NONBLOCK);
|
|
162
|
+
try {
|
|
163
|
+
const before = await handle.stat({ bigint: true });
|
|
164
|
+
if (!before.isFile()) {
|
|
165
|
+
throw new Error("unsafe input: expected a regular file");
|
|
166
|
+
}
|
|
167
|
+
if (before.size > BigInt(maximumBytes)) {
|
|
168
|
+
throw new Error(`maxTokens exceeded: measured ${before.size}, allowed ${maximumBytes}`);
|
|
169
|
+
}
|
|
170
|
+
const buffer = Buffer.allocUnsafe(maximumBytes + 1);
|
|
171
|
+
const bytesRead = await fillBuffer(handle, buffer, 0);
|
|
172
|
+
if (bytesRead > maximumBytes) {
|
|
173
|
+
throw new Error(`maxTokens exceeded: measured at least ${bytesRead}, allowed ${maximumBytes}`);
|
|
174
|
+
}
|
|
175
|
+
const after = await handle.stat({ bigint: true });
|
|
176
|
+
if (after.dev !== before.dev ||
|
|
177
|
+
after.ino !== before.ino ||
|
|
178
|
+
after.size !== before.size ||
|
|
179
|
+
after.mtimeNs !== before.mtimeNs ||
|
|
180
|
+
after.ctimeNs !== before.ctimeNs) {
|
|
181
|
+
throw new Error("unsafe input: file changed during bounded read");
|
|
182
|
+
}
|
|
183
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(buffer.subarray(0, bytesRead));
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
await handle.close();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Fill a fixed buffer from a handle via bounded recursion, returning the total
|
|
191
|
+
* bytes read. Recursion (rather than a mutable accumulator) keeps the module
|
|
192
|
+
* within the repository's immutability lint rules while preserving the
|
|
193
|
+
* original loop's "stop at EOF or buffer end" semantics; depth is bounded by
|
|
194
|
+
* the small fixed buffer, which a learnings file fills in one or two reads.
|
|
195
|
+
* @param handle - Verified regular-file handle
|
|
196
|
+
* @param buffer - Destination buffer sized to the byte ceiling plus one
|
|
197
|
+
* @param offset - Bytes already read into the buffer
|
|
198
|
+
* @returns Total bytes read into the buffer
|
|
199
|
+
*/
|
|
200
|
+
async function fillBuffer(handle, buffer, offset) {
|
|
201
|
+
if (offset >= buffer.length) {
|
|
202
|
+
return offset;
|
|
203
|
+
}
|
|
204
|
+
const result = await handle.read(buffer, offset, buffer.length - offset, null);
|
|
205
|
+
if (result.bytesRead === 0) {
|
|
206
|
+
return offset;
|
|
207
|
+
}
|
|
208
|
+
return fillBuffer(handle, buffer, offset + result.bytesRead);
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=learnings-budget-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"learnings-budget-check.js","sourceRoot":"","sources":["../../src/core/learnings-budget-check.ts"],"names":[],"mappings":"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"}
|
|
@@ -66,7 +66,12 @@ function validateParsedLearningEntry(candidate, index) {
|
|
|
66
66
|
}
|
|
67
67
|
catch (error) {
|
|
68
68
|
const id = readDiagnosticEntryId(candidate);
|
|
69
|
-
|
|
69
|
+
// Single-quote the id rather than JSON.stringify it: the message is later
|
|
70
|
+
// rendered through a JSON-body escaper for terminal safety, which would
|
|
71
|
+
// double-escape embedded double quotes into `\"id\"`. Single quotes read
|
|
72
|
+
// cleanly and are not re-escaped, while control-character neutralization
|
|
73
|
+
// still happens downstream.
|
|
74
|
+
const label = id === undefined ? `entry ${index + 1}` : `entry '${id}'`;
|
|
70
75
|
const detail = error instanceof Error ? error.message : String(error);
|
|
71
76
|
throw new Error(`Invalid learning ${label}: ${detail}`);
|
|
72
77
|
}
|