@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.
Files changed (120) hide show
  1. package/dist/cli/check-learnings-budget-cmd.d.ts +20 -0
  2. package/dist/cli/check-learnings-budget-cmd.d.ts.map +1 -0
  3. package/dist/cli/check-learnings-budget-cmd.js +59 -0
  4. package/dist/cli/check-learnings-budget-cmd.js.map +1 -0
  5. package/dist/cli/index.d.ts +3 -0
  6. package/dist/cli/index.d.ts.map +1 -1
  7. package/dist/cli/index.js +29 -1
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/cli/ui-automations-adapters.d.ts +50 -0
  10. package/dist/cli/ui-automations-adapters.d.ts.map +1 -0
  11. package/dist/cli/ui-automations-adapters.js +162 -0
  12. package/dist/cli/ui-automations-adapters.js.map +1 -0
  13. package/dist/cli/ui-automations.d.ts +89 -0
  14. package/dist/cli/ui-automations.d.ts.map +1 -0
  15. package/dist/cli/ui-automations.js +148 -0
  16. package/dist/cli/ui-automations.js.map +1 -0
  17. package/dist/cli/ui-cmd.d.ts +5 -0
  18. package/dist/cli/ui-cmd.d.ts.map +1 -1
  19. package/dist/cli/ui-cmd.js +17 -0
  20. package/dist/cli/ui-cmd.js.map +1 -1
  21. package/dist/cli/ui-deploy-pipeline-model.d.ts +70 -0
  22. package/dist/cli/ui-deploy-pipeline-model.d.ts.map +1 -0
  23. package/dist/cli/ui-deploy-pipeline-model.js +127 -0
  24. package/dist/cli/ui-deploy-pipeline-model.js.map +1 -0
  25. package/dist/cli/ui-deploy-pipeline.d.ts +25 -0
  26. package/dist/cli/ui-deploy-pipeline.d.ts.map +1 -0
  27. package/dist/cli/ui-deploy-pipeline.js +192 -0
  28. package/dist/cli/ui-deploy-pipeline.js.map +1 -0
  29. package/dist/cli/ui-detected-stacks.d.ts +20 -0
  30. package/dist/cli/ui-detected-stacks.d.ts.map +1 -0
  31. package/dist/cli/ui-detected-stacks.js +28 -0
  32. package/dist/cli/ui-detected-stacks.js.map +1 -0
  33. package/dist/cli/ui-enabled-plugins.d.ts +52 -0
  34. package/dist/cli/ui-enabled-plugins.d.ts.map +1 -0
  35. package/dist/cli/ui-enabled-plugins.js +253 -0
  36. package/dist/cli/ui-enabled-plugins.js.map +1 -0
  37. package/dist/cli/ui-github-repo-expected.d.ts +36 -0
  38. package/dist/cli/ui-github-repo-expected.d.ts.map +1 -0
  39. package/dist/cli/ui-github-repo-expected.js +118 -0
  40. package/dist/cli/ui-github-repo-expected.js.map +1 -0
  41. package/dist/cli/ui-github-repo-gh.d.ts +36 -0
  42. package/dist/cli/ui-github-repo-gh.d.ts.map +1 -0
  43. package/dist/cli/ui-github-repo-gh.js +112 -0
  44. package/dist/cli/ui-github-repo-gh.js.map +1 -0
  45. package/dist/cli/ui-github-repo-map.d.ts +25 -0
  46. package/dist/cli/ui-github-repo-map.d.ts.map +1 -0
  47. package/dist/cli/ui-github-repo-map.js +150 -0
  48. package/dist/cli/ui-github-repo-map.js.map +1 -0
  49. package/dist/cli/ui-github-repo.d.ts +51 -0
  50. package/dist/cli/ui-github-repo.d.ts.map +1 -0
  51. package/dist/cli/ui-github-repo.js +117 -0
  52. package/dist/cli/ui-github-repo.js.map +1 -0
  53. package/dist/cli/ui-observability-checks.d.ts +23 -0
  54. package/dist/cli/ui-observability-checks.d.ts.map +1 -0
  55. package/dist/cli/ui-observability-checks.js +156 -0
  56. package/dist/cli/ui-observability-checks.js.map +1 -0
  57. package/dist/cli/ui-observability-providers.d.ts +69 -0
  58. package/dist/cli/ui-observability-providers.d.ts.map +1 -0
  59. package/dist/cli/ui-observability-providers.js +99 -0
  60. package/dist/cli/ui-observability-providers.js.map +1 -0
  61. package/dist/core/learnings-budget-check.d.ts +42 -0
  62. package/dist/core/learnings-budget-check.d.ts.map +1 -0
  63. package/dist/core/learnings-budget-check.js +210 -0
  64. package/dist/core/learnings-budget-check.js.map +1 -0
  65. package/dist/core/learnings-document.js +6 -1
  66. package/dist/core/learnings-document.js.map +1 -1
  67. package/package.json +1 -1
  68. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  70. package/plugins/lisa-agy/plugin.json +1 -1
  71. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  73. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  74. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  75. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  77. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  78. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  79. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  80. package/plugins/lisa-expo-agy/plugin.json +1 -1
  81. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  82. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  83. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  84. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  85. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  86. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  87. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  88. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  89. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  90. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  91. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  92. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  93. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  94. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  95. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  96. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  97. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  98. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  99. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  100. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  101. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  102. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  103. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  104. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  105. package/plugins/lisa-rails-agy/plugin.json +1 -1
  106. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  107. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  108. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  109. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  110. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  111. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  112. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  113. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  114. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  115. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  116. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  117. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  118. package/scripts/check-learnings-budget.ts +29 -163
  119. package/ui/README.md +5 -3
  120. 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":"AAUA,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,8BAA8B,CAAC;AAqBtC,MAAM,CAAC,MAAM,eAAe,GAAG,QAAQ,CAAC;AACxC,MAAM,CAAC,MAAM,0BAA0B,GAAG,mBAAmB,CAAC;AAC9D,MAAM,CAAC,MAAM,aAAa,GAAG,OAAO,CAAC;AAErC,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAC/B,MAAM,iBAAiB,GAAG,mBAAmB,CAAC;AAC9C,MAAM,sBAAsB,GAC1B,yFAAyF,CAAC;AAC5F,MAAM,mBAAmB,GACvB,wDAAwD,CAAC;AAE3D;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CACnC,OAA0C;IAE1C,IAAI,OAAO,KAAK,WAAW,EAAE,CAAC;QAC5B,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,CAAC;IAC5D,CAAC;IACD,OAAO;QACL,KAAK,EAAE,SAAS;QAChB,MAAM,EAAE,iBAAiB;QACzB,OAAO,EAAE,sBAAsB;KAChC,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAqC,EACrC,SAA8B;IAE9B,IAAI,OAAO,KAAK,mBAAmB,EAAE,CAAC;QACpC,OAAO;YACL,KAAK,EAAE,SAAS;YAChB,MAAM,EAAE,iBAAiB;YACzB,OAAO,EAAE,mBAAmB;SAC7B,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;QAChB,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,CAAC;IAC5D,CAAC;IACD,OAAO;QACL,KAAK,EAAE,OAAO;QACd,KAAK,EAAE,EAAE,MAAM,EAAE,qBAAqB,EAAE,SAAS,EAAE;KACpD,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAC/B,UAEI,EAAE;IAEN,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,kBAAkB,CAAC;IAClD,OAAO;QACL,EAAE,EAAE,eAAe;QACnB,SAAS,EAAE,gBAAgB;QAC3B,GAAG,EAAE,KAAK,EAAC,MAAM,EAAC,EAAE,CAClB,qBAAqB,CAAC,MAAM,KAAK,CAAC,gBAAgB,GAAG,GAAG,EAAE,MAAM,CAAC,CAAC;KACrE,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,2BAA2B,CACzC,UAEI,EAAE;IAEN,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,sBAAsB,CAAC;IACtD,OAAO;QACL,EAAE,EAAE,0BAA0B;QAC9B,SAAS,EAAE,gBAAgB;QAC3B,GAAG,EAAE,KAAK,EAAC,MAAM,EAAC,EAAE,CAClB,gBAAgB,CAAC,MAAM,KAAK,CAAC,gBAAgB,GAAG,GAAG,EAAE,MAAM,CAAC,EAAE,QAAQ,CAAC;KAC1E,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAC7B,UAEI,EAAE;IAEN,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,gBAAgB,CAAC;IAChD,OAAO;QACL,EAAE,EAAE,aAAa;QACjB,SAAS,EAAE,gBAAgB;QAC3B,GAAG,EAAE,KAAK,EAAC,MAAM,EAAC,EAAE,CAClB,gBAAgB,CAAC,MAAM,KAAK,CAAC,gBAAgB,GAAG,GAAG,EAAE,MAAM,CAAC,EAAE,QAAQ,CAAC;KAC1E,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iCAAiC;IAC/C,OAAO;QACL,iBAAiB,EAAE;QACnB,2BAA2B,EAAE;QAC7B,eAAe,EAAE;KAClB,CAAC;AACJ,CAAC"}
@@ -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
- const label = id === undefined ? `entry ${index + 1}` : `entry ${JSON.stringify(id)}`;
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
  }