@xera-ai/core 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/bin/internal.ts +1 -0
  2. package/dist/adapter/types.d.ts.map +1 -0
  3. package/dist/artifact/hash.d.ts.map +1 -0
  4. package/dist/artifact/meta.d.ts +20 -0
  5. package/dist/artifact/meta.d.ts.map +1 -0
  6. package/dist/artifact/paths.d.ts.map +1 -0
  7. package/dist/artifact/status.d.ts +75 -0
  8. package/dist/artifact/status.d.ts.map +1 -0
  9. package/dist/auth/encrypt.d.ts.map +1 -0
  10. package/dist/auth/key.d.ts.map +1 -0
  11. package/dist/auth/refresh.d.ts.map +1 -0
  12. package/dist/{core/src/auth → auth}/state.d.ts +5 -14
  13. package/dist/auth/state.d.ts.map +1 -0
  14. package/dist/bin/internal.js +8607 -373
  15. package/dist/bin-internal/doctor.d.ts.map +1 -0
  16. package/dist/bin-internal/eval-deterministic.d.ts.map +1 -0
  17. package/dist/bin-internal/eval-prepare.d.ts.map +1 -0
  18. package/dist/bin-internal/eval-report.d.ts.map +1 -0
  19. package/dist/bin-internal/exec.d.ts.map +1 -0
  20. package/dist/bin-internal/fetch.d.ts.map +1 -0
  21. package/dist/bin-internal/graph-backfill.d.ts +2 -0
  22. package/dist/bin-internal/graph-backfill.d.ts.map +1 -0
  23. package/dist/bin-internal/graph-enrich.d.ts +2 -0
  24. package/dist/bin-internal/graph-enrich.d.ts.map +1 -0
  25. package/dist/bin-internal/graph-query.d.ts +2 -0
  26. package/dist/bin-internal/graph-query.d.ts.map +1 -0
  27. package/dist/bin-internal/graph-record-script.d.ts +2 -0
  28. package/dist/bin-internal/graph-record-script.d.ts.map +1 -0
  29. package/dist/bin-internal/graph-record.d.ts +3 -0
  30. package/dist/bin-internal/graph-record.d.ts.map +1 -0
  31. package/dist/bin-internal/graph-snapshot.d.ts +2 -0
  32. package/dist/bin-internal/graph-snapshot.d.ts.map +1 -0
  33. package/dist/bin-internal/heal-prepare.d.ts.map +1 -0
  34. package/dist/bin-internal/index.d.ts.map +1 -0
  35. package/dist/bin-internal/lint.d.ts.map +1 -0
  36. package/dist/bin-internal/normalize.d.ts.map +1 -0
  37. package/dist/bin-internal/post.d.ts.map +1 -0
  38. package/dist/bin-internal/promote.d.ts.map +1 -0
  39. package/dist/bin-internal/report.d.ts.map +1 -0
  40. package/dist/bin-internal/status-cmd.d.ts.map +1 -0
  41. package/dist/bin-internal/typecheck.d.ts.map +1 -0
  42. package/dist/bin-internal/unlock.d.ts.map +1 -0
  43. package/dist/bin-internal/validate-feature.d.ts.map +1 -0
  44. package/dist/bin-internal/verify-prompts.d.ts.map +1 -0
  45. package/dist/classifier/aggregate.d.ts.map +1 -0
  46. package/dist/classifier/history.d.ts.map +1 -0
  47. package/dist/classifier/types.d.ts.map +1 -0
  48. package/dist/config/define.d.ts.map +1 -0
  49. package/dist/config/load.d.ts.map +1 -0
  50. package/dist/config/schema.d.ts +66 -0
  51. package/dist/config/schema.d.ts.map +1 -0
  52. package/dist/eval/paths.d.ts.map +1 -0
  53. package/dist/eval/run-id.d.ts.map +1 -0
  54. package/dist/eval/types.d.ts +203 -0
  55. package/dist/eval/types.d.ts.map +1 -0
  56. package/dist/graph/classify.d.ts +42 -0
  57. package/dist/graph/classify.d.ts.map +1 -0
  58. package/dist/graph/cost.d.ts +21 -0
  59. package/dist/graph/cost.d.ts.map +1 -0
  60. package/dist/graph/enrich.d.ts +10 -0
  61. package/dist/graph/enrich.d.ts.map +1 -0
  62. package/dist/graph/index.d.ts +13 -0
  63. package/dist/graph/index.d.ts.map +1 -0
  64. package/dist/graph/paths.d.ts +10 -0
  65. package/dist/graph/paths.d.ts.map +1 -0
  66. package/dist/graph/schema.d.ts +180 -0
  67. package/dist/graph/schema.d.ts.map +1 -0
  68. package/dist/graph/similarity.d.ts +3 -0
  69. package/dist/graph/similarity.d.ts.map +1 -0
  70. package/dist/graph/store.d.ts +14 -0
  71. package/dist/graph/store.d.ts.map +1 -0
  72. package/dist/graph/types.d.ts +151 -0
  73. package/dist/graph/types.d.ts.map +1 -0
  74. package/dist/graph/ulid.d.ts +2 -0
  75. package/dist/graph/ulid.d.ts.map +1 -0
  76. package/dist/{core/src/index.d.ts → index.d.ts} +11 -11
  77. package/dist/index.d.ts.map +1 -0
  78. package/dist/jira/client.d.ts.map +1 -0
  79. package/dist/jira/fields.d.ts.map +1 -0
  80. package/dist/jira/mcp-backend.d.ts.map +1 -0
  81. package/dist/jira/rest-backend.d.ts.map +1 -0
  82. package/dist/jira/retry.d.ts.map +1 -0
  83. package/dist/jira/types.d.ts.map +1 -0
  84. package/dist/lock/file-lock.d.ts.map +1 -0
  85. package/dist/logging/ndjson-logger.d.ts.map +1 -0
  86. package/dist/reporter/jira-comment.d.ts.map +1 -0
  87. package/dist/reporter/status-writer.d.ts.map +1 -0
  88. package/dist/src/index.js +346 -318
  89. package/package.json +19 -14
  90. package/src/artifact/status.ts +8 -1
  91. package/src/auth/refresh.ts +1 -0
  92. package/src/bin-internal/doctor.ts +37 -1
  93. package/src/bin-internal/eval-prepare.ts +1 -1
  94. package/src/bin-internal/graph-backfill.ts +43 -0
  95. package/src/bin-internal/graph-enrich.ts +28 -0
  96. package/src/bin-internal/graph-query.ts +43 -0
  97. package/src/bin-internal/graph-record-script.ts +191 -0
  98. package/src/bin-internal/graph-record.ts +287 -0
  99. package/src/bin-internal/graph-snapshot.ts +23 -0
  100. package/src/bin-internal/heal-prepare.ts +1 -1
  101. package/src/bin-internal/index.ts +10 -0
  102. package/src/bin-internal/report.ts +63 -5
  103. package/src/bin-internal/verify-prompts.ts +3 -0
  104. package/src/classifier/aggregate.ts +1 -0
  105. package/src/config/schema.ts +6 -6
  106. package/src/graph/classify.ts +126 -0
  107. package/src/graph/cost.ts +59 -0
  108. package/src/graph/enrich.ts +103 -0
  109. package/src/graph/index.ts +30 -0
  110. package/src/graph/paths.ts +27 -0
  111. package/src/graph/schema.ts +142 -0
  112. package/src/graph/similarity.ts +43 -0
  113. package/src/graph/store.ts +231 -0
  114. package/src/graph/types.ts +179 -0
  115. package/src/graph/ulid.ts +58 -0
  116. package/src/index.ts +11 -11
  117. package/src/jira/rest-backend.ts +1 -1
  118. package/src/reporter/status-writer.ts +1 -1
  119. package/dist/core/src/adapter/types.d.ts.map +0 -1
  120. package/dist/core/src/artifact/hash.d.ts.map +0 -1
  121. package/dist/core/src/artifact/meta.d.ts +0 -46
  122. package/dist/core/src/artifact/meta.d.ts.map +0 -1
  123. package/dist/core/src/artifact/paths.d.ts.map +0 -1
  124. package/dist/core/src/artifact/status.d.ts +0 -96
  125. package/dist/core/src/artifact/status.d.ts.map +0 -1
  126. package/dist/core/src/auth/encrypt.d.ts.map +0 -1
  127. package/dist/core/src/auth/key.d.ts.map +0 -1
  128. package/dist/core/src/auth/refresh.d.ts.map +0 -1
  129. package/dist/core/src/auth/state.d.ts.map +0 -1
  130. package/dist/core/src/bin-internal/doctor.d.ts.map +0 -1
  131. package/dist/core/src/bin-internal/eval-deterministic.d.ts.map +0 -1
  132. package/dist/core/src/bin-internal/eval-prepare.d.ts.map +0 -1
  133. package/dist/core/src/bin-internal/eval-report.d.ts.map +0 -1
  134. package/dist/core/src/bin-internal/exec.d.ts.map +0 -1
  135. package/dist/core/src/bin-internal/fetch.d.ts.map +0 -1
  136. package/dist/core/src/bin-internal/heal-prepare.d.ts.map +0 -1
  137. package/dist/core/src/bin-internal/index.d.ts.map +0 -1
  138. package/dist/core/src/bin-internal/lint.d.ts.map +0 -1
  139. package/dist/core/src/bin-internal/normalize.d.ts.map +0 -1
  140. package/dist/core/src/bin-internal/post.d.ts.map +0 -1
  141. package/dist/core/src/bin-internal/promote.d.ts.map +0 -1
  142. package/dist/core/src/bin-internal/report.d.ts.map +0 -1
  143. package/dist/core/src/bin-internal/status-cmd.d.ts.map +0 -1
  144. package/dist/core/src/bin-internal/typecheck.d.ts.map +0 -1
  145. package/dist/core/src/bin-internal/unlock.d.ts.map +0 -1
  146. package/dist/core/src/bin-internal/validate-feature.d.ts.map +0 -1
  147. package/dist/core/src/bin-internal/verify-prompts.d.ts.map +0 -1
  148. package/dist/core/src/classifier/aggregate.d.ts.map +0 -1
  149. package/dist/core/src/classifier/history.d.ts.map +0 -1
  150. package/dist/core/src/classifier/types.d.ts.map +0 -1
  151. package/dist/core/src/config/define.d.ts.map +0 -1
  152. package/dist/core/src/config/load.d.ts.map +0 -1
  153. package/dist/core/src/config/schema.d.ts +0 -326
  154. package/dist/core/src/config/schema.d.ts.map +0 -1
  155. package/dist/core/src/eval/paths.d.ts.map +0 -1
  156. package/dist/core/src/eval/run-id.d.ts.map +0 -1
  157. package/dist/core/src/eval/types.d.ts +0 -551
  158. package/dist/core/src/eval/types.d.ts.map +0 -1
  159. package/dist/core/src/index.d.ts.map +0 -1
  160. package/dist/core/src/jira/client.d.ts.map +0 -1
  161. package/dist/core/src/jira/fields.d.ts.map +0 -1
  162. package/dist/core/src/jira/mcp-backend.d.ts.map +0 -1
  163. package/dist/core/src/jira/rest-backend.d.ts.map +0 -1
  164. package/dist/core/src/jira/retry.d.ts.map +0 -1
  165. package/dist/core/src/jira/types.d.ts.map +0 -1
  166. package/dist/core/src/lock/file-lock.d.ts.map +0 -1
  167. package/dist/core/src/logging/ndjson-logger.d.ts.map +0 -1
  168. package/dist/core/src/reporter/jira-comment.d.ts.map +0 -1
  169. package/dist/core/src/reporter/status-writer.d.ts.map +0 -1
  170. package/dist/web/src/adapter.d.ts +0 -3
  171. package/dist/web/src/adapter.d.ts.map +0 -1
  172. package/dist/web/src/auth-setup/define.d.ts +0 -16
  173. package/dist/web/src/auth-setup/define.d.ts.map +0 -1
  174. package/dist/web/src/auth-setup/playwright-state.d.ts +0 -2
  175. package/dist/web/src/auth-setup/playwright-state.d.ts.map +0 -1
  176. package/dist/web/src/auth-setup/runner.d.ts +0 -12
  177. package/dist/web/src/auth-setup/runner.d.ts.map +0 -1
  178. package/dist/web/src/executor/index.d.ts +0 -18
  179. package/dist/web/src/executor/index.d.ts.map +0 -1
  180. package/dist/web/src/executor/playwright-args.d.ts +0 -7
  181. package/dist/web/src/executor/playwright-args.d.ts.map +0 -1
  182. package/dist/web/src/generator/gherkin-validate.d.ts +0 -9
  183. package/dist/web/src/generator/gherkin-validate.d.ts.map +0 -1
  184. package/dist/web/src/generator/lint.d.ts +0 -9
  185. package/dist/web/src/generator/lint.d.ts.map +0 -1
  186. package/dist/web/src/generator/pom-scan.d.ts +0 -6
  187. package/dist/web/src/generator/pom-scan.d.ts.map +0 -1
  188. package/dist/web/src/generator/promote.d.ts +0 -7
  189. package/dist/web/src/generator/promote.d.ts.map +0 -1
  190. package/dist/web/src/generator/selector-rules.d.ts +0 -10
  191. package/dist/web/src/generator/selector-rules.d.ts.map +0 -1
  192. package/dist/web/src/generator/typecheck.d.ts +0 -11
  193. package/dist/web/src/generator/typecheck.d.ts.map +0 -1
  194. package/dist/web/src/index.d.ts +0 -18
  195. package/dist/web/src/index.d.ts.map +0 -1
  196. package/dist/web/src/trace-normalizer/normalize.d.ts +0 -7
  197. package/dist/web/src/trace-normalizer/normalize.d.ts.map +0 -1
  198. package/dist/web/src/trace-normalizer/parse.d.ts +0 -37
  199. package/dist/web/src/trace-normalizer/parse.d.ts.map +0 -1
  200. package/dist/web/src/trace-normalizer/scrub-rules.d.ts +0 -12
  201. package/dist/web/src/trace-normalizer/scrub-rules.d.ts.map +0 -1
  202. package/dist/web/src/trace-normalizer/scrub.d.ts +0 -29
  203. package/dist/web/src/trace-normalizer/scrub.d.ts.map +0 -1
  204. package/dist/web/src/trace-normalizer/unzip.d.ts +0 -6
  205. package/dist/web/src/trace-normalizer/unzip.d.ts.map +0 -1
  206. /package/dist/{core/src/adapter → adapter}/types.d.ts +0 -0
  207. /package/dist/{core/src/artifact → artifact}/hash.d.ts +0 -0
  208. /package/dist/{core/src/artifact → artifact}/paths.d.ts +0 -0
  209. /package/dist/{core/src/auth → auth}/encrypt.d.ts +0 -0
  210. /package/dist/{core/src/auth → auth}/key.d.ts +0 -0
  211. /package/dist/{core/src/auth → auth}/refresh.d.ts +0 -0
  212. /package/dist/{core/src/bin-internal → bin-internal}/doctor.d.ts +0 -0
  213. /package/dist/{core/src/bin-internal → bin-internal}/eval-deterministic.d.ts +0 -0
  214. /package/dist/{core/src/bin-internal → bin-internal}/eval-prepare.d.ts +0 -0
  215. /package/dist/{core/src/bin-internal → bin-internal}/eval-report.d.ts +0 -0
  216. /package/dist/{core/src/bin-internal → bin-internal}/exec.d.ts +0 -0
  217. /package/dist/{core/src/bin-internal → bin-internal}/fetch.d.ts +0 -0
  218. /package/dist/{core/src/bin-internal → bin-internal}/heal-prepare.d.ts +0 -0
  219. /package/dist/{core/src/bin-internal → bin-internal}/index.d.ts +0 -0
  220. /package/dist/{core/src/bin-internal → bin-internal}/lint.d.ts +0 -0
  221. /package/dist/{core/src/bin-internal → bin-internal}/normalize.d.ts +0 -0
  222. /package/dist/{core/src/bin-internal → bin-internal}/post.d.ts +0 -0
  223. /package/dist/{core/src/bin-internal → bin-internal}/promote.d.ts +0 -0
  224. /package/dist/{core/src/bin-internal → bin-internal}/report.d.ts +0 -0
  225. /package/dist/{core/src/bin-internal → bin-internal}/status-cmd.d.ts +0 -0
  226. /package/dist/{core/src/bin-internal → bin-internal}/typecheck.d.ts +0 -0
  227. /package/dist/{core/src/bin-internal → bin-internal}/unlock.d.ts +0 -0
  228. /package/dist/{core/src/bin-internal → bin-internal}/validate-feature.d.ts +0 -0
  229. /package/dist/{core/src/bin-internal → bin-internal}/verify-prompts.d.ts +0 -0
  230. /package/dist/{core/src/classifier → classifier}/aggregate.d.ts +0 -0
  231. /package/dist/{core/src/classifier → classifier}/history.d.ts +0 -0
  232. /package/dist/{core/src/classifier → classifier}/types.d.ts +0 -0
  233. /package/dist/{core/src/config → config}/define.d.ts +0 -0
  234. /package/dist/{core/src/config → config}/load.d.ts +0 -0
  235. /package/dist/{core/src/eval → eval}/paths.d.ts +0 -0
  236. /package/dist/{core/src/eval → eval}/run-id.d.ts +0 -0
  237. /package/dist/{core/src/jira → jira}/client.d.ts +0 -0
  238. /package/dist/{core/src/jira → jira}/fields.d.ts +0 -0
  239. /package/dist/{core/src/jira → jira}/mcp-backend.d.ts +0 -0
  240. /package/dist/{core/src/jira → jira}/rest-backend.d.ts +0 -0
  241. /package/dist/{core/src/jira → jira}/retry.d.ts +0 -0
  242. /package/dist/{core/src/jira → jira}/types.d.ts +0 -0
  243. /package/dist/{core/src/lock → lock}/file-lock.d.ts +0 -0
  244. /package/dist/{core/src/logging → logging}/ndjson-logger.d.ts +0 -0
  245. /package/dist/{core/src/reporter → reporter}/jira-comment.d.ts +0 -0
  246. /package/dist/{core/src/reporter → reporter}/status-writer.d.ts +0 -0
package/dist/src/index.js CHANGED
@@ -1,120 +1,25 @@
1
1
  // @bun
2
- // src/config/schema.ts
3
- import { z } from "zod";
4
- var AuthRoleSchema = z.object({
5
- envEmail: z.string().min(1),
6
- envPassword: z.string().min(1)
7
- });
8
- var AuthSchema = z.object({
9
- strategy: z.enum(["storageState", "apiToken", "none"]).default("none"),
10
- ttl: z.string().default("8h"),
11
- refreshBuffer: z.string().default("30m"),
12
- setupScript: z.string().optional(),
13
- roles: z.record(z.string(), AuthRoleSchema).default({})
14
- });
15
- var WebSchema = z.object({
16
- baseUrl: z.record(z.string(), z.string().url()).refine((m) => Object.keys(m).length > 0, {
17
- message: "baseUrl must have at least one environment"
18
- }),
19
- defaultEnv: z.string(),
20
- auth: AuthSchema.default({}),
21
- testData: z.object({
22
- users: z.record(z.string(), z.object({ fromAuth: z.string() })).default({})
23
- }).default({ users: {} })
24
- }).refine((w) => w.baseUrl[w.defaultEnv] !== undefined, {
25
- message: "defaultEnv must exist in baseUrl map",
26
- path: ["defaultEnv"]
27
- });
28
- var JiraSchema = z.object({
29
- baseUrl: z.string().url(),
30
- projectKeys: z.array(z.string().min(1)).min(1),
31
- fields: z.object({
32
- story: z.string().min(1),
33
- acceptanceCriteria: z.string().optional(),
34
- attachments: z.string().default("attachment")
35
- })
36
- });
37
- var AISchema = z.object({
38
- livePageSnapshot: z.boolean().default(true),
39
- confidenceThreshold: z.enum(["low", "medium", "high"]).default("medium"),
40
- maxRetries: z.object({
41
- typecheck: z.number().int().min(0).max(5).default(2),
42
- lint: z.number().int().min(0).max(5).default(2),
43
- validateFeature: z.number().int().min(0).max(5).default(2)
44
- }).default({})
45
- }).default({});
46
- var ReportingSchema = z.object({
47
- language: z.enum(["en", "vi"]).default("en"),
48
- postToJira: z.boolean().default(true),
49
- transition: z.object({
50
- onPass: z.string().nullable().default(null),
51
- onFail: z.string().nullable().default(null)
52
- }).default({}),
53
- artifactLinks: z.enum(["git", "local"]).default("git")
54
- }).default({});
55
- var XeraConfigSchema = z.object({
56
- jira: JiraSchema,
57
- web: WebSchema,
58
- ai: AISchema,
59
- reporting: ReportingSchema,
60
- adapters: z.array(z.string().min(1)).min(1).default(["web"])
61
- });
62
- // src/config/define.ts
63
- function defineConfig(config) {
64
- return config;
65
- }
66
- // src/config/load.ts
67
- import { existsSync } from "fs";
68
- import { join } from "path";
69
- import { pathToFileURL } from "url";
70
- async function loadConfig(cwd) {
71
- const path = join(cwd, "xera.config.ts");
72
- if (!existsSync(path)) {
73
- throw new Error(`xera.config.ts not found in ${cwd}`);
74
- }
75
- const mod = await import(pathToFileURL(path).href);
76
- const raw = mod.default ?? mod;
77
- return XeraConfigSchema.parse(raw);
78
- }
79
- // src/artifact/paths.ts
80
- import { join as join2 } from "path";
81
- var TICKET_RE = /^[A-Z][A-Z0-9_]*-\d+$|^SAMPLE-\d+$/;
82
- function resolveArtifactPaths(repoRoot, ticket) {
83
- if (!TICKET_RE.test(ticket)) {
84
- throw new Error(`Invalid ticket key: "${ticket}" (expected e.g. JIRA-123 or SAMPLE-001)`);
85
- }
86
- const ticketDir = join2(repoRoot, ".xera", ticket);
87
- return {
88
- ticketDir,
89
- storyPath: join2(ticketDir, "story.md"),
90
- featurePath: join2(ticketDir, "test.feature"),
91
- specPath: join2(ticketDir, "spec.ts"),
92
- pageObjectsDir: join2(ticketDir, "page-objects"),
93
- runsDir: join2(ticketDir, "runs"),
94
- metaPath: join2(ticketDir, "meta.json"),
95
- statusPath: join2(ticketDir, "status.json"),
96
- logPath: join2(ticketDir, "xera.log"),
97
- lockPath: join2(ticketDir, ".lock"),
98
- authDir: join2(repoRoot, ".xera", ".auth"),
99
- runPath: (runId) => {
100
- const runDir = join2(ticketDir, "runs", runId);
101
- return {
102
- runDir,
103
- reportJsonPath: join2(runDir, "report.json"),
104
- tracePath: join2(runDir, "trace.zip"),
105
- normalizedPath: join2(runDir, "normalized.json"),
106
- screenshotsDir: join2(runDir, "screenshots"),
107
- videoDir: join2(runDir, "videos")
108
- };
109
- }
110
- };
111
- }
112
- function generateRunId(now = new Date) {
113
- return now.toISOString().replace(/[:.]/g, "-").replace("Z", "");
114
- }
2
+ var __defProp = Object.defineProperty;
3
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
4
+ var __returnValue = (v) => v;
5
+ function __exportSetter(name, newValue) {
6
+ this[name] = __returnValue.bind(null, newValue);
7
+ }
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, {
11
+ get: all[name],
12
+ enumerable: true,
13
+ configurable: true,
14
+ set: __exportSetter.bind(all, name)
15
+ });
16
+ };
17
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
18
+ var __require = import.meta.require;
19
+
115
20
  // src/artifact/hash.ts
116
21
  import { createHash } from "crypto";
117
- import { existsSync as existsSync2, readFileSync } from "fs";
22
+ import { existsSync, readFileSync } from "fs";
118
23
  function hashString(s) {
119
24
  return `sha256:${createHash("sha256").update(s).digest("hex")}`;
120
25
  }
@@ -122,30 +27,30 @@ function hashFile(path) {
122
27
  return hashString(readFileSync(path, "utf8"));
123
28
  }
124
29
  function hashFileIfExists(path) {
125
- if (!existsSync2(path))
30
+ if (!existsSync(path))
126
31
  return null;
127
32
  return hashFile(path);
128
33
  }
129
34
  // src/artifact/meta.ts
130
- import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
35
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
131
36
  import { dirname } from "path";
132
- import { z as z2 } from "zod";
133
- var MetaJsonSchema = z2.object({
134
- ticket: z2.string(),
135
- adapter: z2.string(),
136
- xera_version: z2.string(),
137
- prompts_version: z2.string(),
138
- fetched_at: z2.string().optional(),
139
- story_hash: z2.string().optional(),
140
- feature_generated_at: z2.string().optional(),
141
- feature_generated_from_story_hash: z2.string().optional(),
142
- feature_hash: z2.string().optional(),
143
- script_generated_at: z2.string().optional(),
144
- script_generated_from_feature_hash: z2.string().optional(),
145
- script_warnings: z2.array(z2.string()).optional()
37
+ import { z } from "zod";
38
+ var MetaJsonSchema = z.object({
39
+ ticket: z.string(),
40
+ adapter: z.string(),
41
+ xera_version: z.string(),
42
+ prompts_version: z.string(),
43
+ fetched_at: z.string().optional(),
44
+ story_hash: z.string().optional(),
45
+ feature_generated_at: z.string().optional(),
46
+ feature_generated_from_story_hash: z.string().optional(),
47
+ feature_hash: z.string().optional(),
48
+ script_generated_at: z.string().optional(),
49
+ script_generated_from_feature_hash: z.string().optional(),
50
+ script_warnings: z.array(z.string()).optional()
146
51
  });
147
52
  function readMeta(path) {
148
- if (!existsSync3(path))
53
+ if (!existsSync2(path))
149
54
  return null;
150
55
  return MetaJsonSchema.parse(JSON.parse(readFileSync2(path, "utf8")));
151
56
  }
@@ -162,36 +67,79 @@ function updateMeta(path, patch) {
162
67
  writeMeta(path, next);
163
68
  return next;
164
69
  }
70
+ // src/artifact/paths.ts
71
+ import { join } from "path";
72
+ var TICKET_RE = /^[A-Z][A-Z0-9_]*-\d+$|^SAMPLE-\d+$/;
73
+ function resolveArtifactPaths(repoRoot, ticket) {
74
+ if (!TICKET_RE.test(ticket)) {
75
+ throw new Error(`Invalid ticket key: "${ticket}" (expected e.g. JIRA-123 or SAMPLE-001)`);
76
+ }
77
+ const ticketDir = join(repoRoot, ".xera", ticket);
78
+ return {
79
+ ticketDir,
80
+ storyPath: join(ticketDir, "story.md"),
81
+ featurePath: join(ticketDir, "test.feature"),
82
+ specPath: join(ticketDir, "spec.ts"),
83
+ pageObjectsDir: join(ticketDir, "page-objects"),
84
+ runsDir: join(ticketDir, "runs"),
85
+ metaPath: join(ticketDir, "meta.json"),
86
+ statusPath: join(ticketDir, "status.json"),
87
+ logPath: join(ticketDir, "xera.log"),
88
+ lockPath: join(ticketDir, ".lock"),
89
+ authDir: join(repoRoot, ".xera", ".auth"),
90
+ runPath: (runId) => {
91
+ const runDir = join(ticketDir, "runs", runId);
92
+ return {
93
+ runDir,
94
+ reportJsonPath: join(runDir, "report.json"),
95
+ tracePath: join(runDir, "trace.zip"),
96
+ normalizedPath: join(runDir, "normalized.json"),
97
+ screenshotsDir: join(runDir, "screenshots"),
98
+ videoDir: join(runDir, "videos")
99
+ };
100
+ }
101
+ };
102
+ }
103
+ function generateRunId(now = new Date) {
104
+ return now.toISOString().replace(/[:.]/g, "-").replace("Z", "");
105
+ }
165
106
  // src/artifact/status.ts
166
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
107
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
167
108
  import { dirname as dirname2 } from "path";
168
- import { z as z3 } from "zod";
169
- var ClassificationEnum = z3.enum(["PASS", "REAL_BUG", "SELECTOR_DRIFT", "FLAKY", "TEST_BUG"]);
170
- var ResultEnum = z3.enum(["PASS", "FAIL"]);
171
- var ConfidenceEnum = z3.enum(["low", "medium", "high"]);
172
- var HistoryEntrySchema = z3.object({
173
- ts: z3.string(),
109
+ import { z as z2 } from "zod";
110
+ var ClassificationEnum = z2.enum([
111
+ "PASS",
112
+ "REAL_BUG",
113
+ "SELECTOR_DRIFT",
114
+ "FLAKY",
115
+ "TEST_BUG",
116
+ "TEST_OUTDATED"
117
+ ]);
118
+ var ResultEnum = z2.enum(["PASS", "FAIL"]);
119
+ var ConfidenceEnum = z2.enum(["low", "medium", "high"]);
120
+ var HistoryEntrySchema = z2.object({
121
+ ts: z2.string(),
174
122
  result: ResultEnum,
175
123
  class: ClassificationEnum
176
124
  });
177
- var StatusJsonSchema = z3.object({
178
- ticket: z3.string(),
179
- lastRun: z3.string(),
125
+ var StatusJsonSchema = z2.object({
126
+ ticket: z2.string(),
127
+ lastRun: z2.string(),
180
128
  result: ResultEnum,
181
129
  classification: ClassificationEnum,
182
130
  confidence: ConfidenceEnum,
183
- scenarios: z3.object({
184
- total: z3.number().int().nonnegative(),
185
- passed: z3.number().int().nonnegative(),
186
- failed: z3.number().int().nonnegative(),
187
- skipped: z3.number().int().nonnegative()
131
+ scenarios: z2.object({
132
+ total: z2.number().int().nonnegative(),
133
+ passed: z2.number().int().nonnegative(),
134
+ failed: z2.number().int().nonnegative(),
135
+ skipped: z2.number().int().nonnegative()
188
136
  }),
189
- history: z3.array(HistoryEntrySchema).default([]),
190
- last_jira_comment_id: z3.string().optional()
137
+ history: z2.array(HistoryEntrySchema).default([]),
138
+ last_jira_comment_id: z2.string().optional()
191
139
  });
192
140
  var HISTORY_CAP = 20;
193
141
  function readStatus(path) {
194
- if (!existsSync4(path))
142
+ if (!existsSync3(path))
195
143
  return null;
196
144
  return StatusJsonSchema.parse(JSON.parse(readFileSync3(path, "utf8")));
197
145
  }
@@ -208,105 +156,219 @@ function appendHistory(path, entry) {
208
156
  writeStatus(path, s);
209
157
  return s;
210
158
  }
211
- // src/logging/ndjson-logger.ts
212
- import { appendFileSync, existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync4 } from "fs";
213
- import { dirname as dirname3 } from "path";
214
-
215
- class NdjsonLogger {
216
- path;
217
- constructor(path) {
218
- this.path = path;
219
- mkdirSync3(dirname3(path), { recursive: true });
220
- }
221
- log(payload) {
222
- const entry = { ts: new Date().toISOString(), ...payload };
223
- appendFileSync(this.path, `${JSON.stringify(entry)}
224
- `);
159
+ // src/auth/encrypt.ts
160
+ import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
161
+ var ALGO = "aes-256-gcm";
162
+ var KEY_LEN = 32;
163
+ var IV_LEN = 12;
164
+ var TAG_LEN = 16;
165
+ var VERSION = "v1";
166
+ function generateKey() {
167
+ return randomBytes(KEY_LEN).toString("hex");
168
+ }
169
+ function keyToBuf(key) {
170
+ const buf = Buffer.from(key, "hex");
171
+ if (buf.length !== KEY_LEN)
172
+ throw new Error(`Key must be ${KEY_LEN} bytes (got ${buf.length})`);
173
+ return buf;
174
+ }
175
+ function encrypt(plaintext, keyHex) {
176
+ const key = keyToBuf(keyHex);
177
+ const iv = randomBytes(IV_LEN);
178
+ const cipher = createCipheriv(ALGO, key, iv);
179
+ const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
180
+ const tag = cipher.getAuthTag();
181
+ return `${VERSION}:${iv.toString("base64")}:${tag.toString("base64")}:${ct.toString("base64")}`;
182
+ }
183
+ function decrypt(ciphertext, keyHex) {
184
+ const [version, ivB64, tagB64, ctB64] = ciphertext.split(":");
185
+ if (version !== VERSION)
186
+ throw new Error(`Unsupported ciphertext version: ${version}`);
187
+ if (!ivB64 || !tagB64 || !ctB64)
188
+ throw new Error("Malformed ciphertext");
189
+ const key = keyToBuf(keyHex);
190
+ const iv = Buffer.from(ivB64, "base64");
191
+ const tag = Buffer.from(tagB64, "base64");
192
+ const ct = Buffer.from(ctB64, "base64");
193
+ if (tag.length !== TAG_LEN)
194
+ throw new Error("Bad auth tag length");
195
+ const decipher = createDecipheriv(ALGO, key, iv);
196
+ decipher.setAuthTag(tag);
197
+ return Buffer.concat([decipher.update(ct), decipher.final()]).toString("utf8");
198
+ }
199
+ // src/auth/key.ts
200
+ var AUTH_KEY_ENV = "XERA_AUTH_KEY";
201
+ function resolveAuthKey() {
202
+ const key = process.env[AUTH_KEY_ENV];
203
+ if (!key) {
204
+ throw new Error(`${AUTH_KEY_ENV} not set. It is auto-generated by \`xera init\` and saved to .env. If you deleted .env, regenerate it by running \`xera init --update\` \u2014 note that any cached auth state will be invalidated.`);
225
205
  }
226
- static readAll(path) {
227
- if (!existsSync5(path))
228
- return [];
229
- const txt = readFileSync4(path, "utf8").trim();
230
- if (!txt)
231
- return [];
232
- return txt.split(`
233
- `).map((line) => JSON.parse(line));
206
+ if (!/^[0-9a-f]{64}$/i.test(key)) {
207
+ throw new Error(`${AUTH_KEY_ENV} must be a 64-character hex string (32 bytes).`);
234
208
  }
209
+ return key;
235
210
  }
236
- // src/lock/file-lock.ts
237
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
238
- import { hostname } from "os";
239
- import { dirname as dirname4 } from "path";
240
- function acquireLock(path, runId) {
241
- if (existsSync6(path))
242
- return false;
243
- mkdirSync4(dirname4(path), { recursive: true });
244
- const data = {
245
- pid: process.pid,
246
- hostname: hostname(),
247
- started_at: new Date().toISOString(),
248
- run_id: runId
249
- };
250
- try {
251
- writeFileSync3(path, JSON.stringify(data), { flag: "wx" });
211
+ // src/auth/refresh.ts
212
+ var RE = /^(\d+)([hms])$/;
213
+ function parseDuration(d) {
214
+ const m = RE.exec(d);
215
+ if (!m)
216
+ throw new Error(`Bad duration "${d}" \u2014 expected e.g. "8h", "30m", "45s"`);
217
+ const n = Number(m[1]);
218
+ const unit = m[2];
219
+ if (unit === "h")
220
+ return n * 3600 * 1000;
221
+ if (unit === "m")
222
+ return n * 60 * 1000;
223
+ return n * 1000;
224
+ }
225
+ function needsRefresh(entry, policy, now = new Date) {
226
+ if (!entry)
252
227
  return true;
253
- } catch {
254
- return false;
255
- }
228
+ const ttlMs = parseDuration(policy.ttl);
229
+ const bufMs = parseDuration(policy.refreshBuffer);
230
+ const createdAt = new Date(entry.created_at).getTime();
231
+ if (now.getTime() - createdAt > ttlMs)
232
+ return true;
233
+ const expiresAt = new Date(entry.expires_at).getTime();
234
+ if (expiresAt - now.getTime() < bufMs)
235
+ return true;
236
+ return false;
256
237
  }
257
- function releaseLock(path) {
258
- if (existsSync6(path))
259
- unlinkSync(path);
238
+ // src/auth/state.ts
239
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
240
+ import { join as join2 } from "path";
241
+ import { z as z3 } from "zod";
242
+ var AuthStateEntrySchema = z3.object({
243
+ role: z3.string(),
244
+ strategy: z3.enum(["storageState", "apiToken"]),
245
+ created_at: z3.string(),
246
+ expires_at: z3.string(),
247
+ payload: z3.record(z3.string(), z3.unknown())
248
+ });
249
+ function pathFor(authDir, role) {
250
+ return join2(authDir, `${role}.json`);
260
251
  }
261
- function readLock(path) {
262
- if (!existsSync6(path))
252
+ function writeAuthState(authDir, entry) {
253
+ mkdirSync3(authDir, { recursive: true });
254
+ const ct = encrypt(JSON.stringify(entry), resolveAuthKey());
255
+ writeFileSync3(pathFor(authDir, entry.role), ct);
256
+ }
257
+ function readAuthState(authDir, role) {
258
+ const p = pathFor(authDir, role);
259
+ if (!existsSync4(p))
263
260
  return null;
264
- return JSON.parse(readFileSync5(path, "utf8"));
261
+ const txt = readFileSync4(p, "utf8");
262
+ const plain = decrypt(txt, resolveAuthKey());
263
+ return AuthStateEntrySchema.parse(JSON.parse(plain));
265
264
  }
266
- function isLockStale(path) {
267
- const lock = readLock(path);
268
- if (!lock)
269
- return true;
270
- if (lock.hostname !== hostname()) {
271
- return false;
272
- }
273
- try {
274
- process.kill(lock.pid, 0);
275
- return false;
276
- } catch {
277
- return true;
278
- }
265
+ // src/config/define.ts
266
+ function defineConfig(config) {
267
+ return config;
279
268
  }
280
- function forceUnlock(path) {
281
- releaseLock(path);
269
+ // src/config/load.ts
270
+ import { existsSync as existsSync5 } from "fs";
271
+ import { join as join3 } from "path";
272
+ import { pathToFileURL } from "url";
273
+
274
+ // src/config/schema.ts
275
+ import { z as z4 } from "zod";
276
+ var AuthRoleSchema = z4.object({
277
+ envEmail: z4.string().min(1),
278
+ envPassword: z4.string().min(1)
279
+ });
280
+ var AuthSchema = z4.object({
281
+ strategy: z4.enum(["storageState", "apiToken", "none"]).default("none"),
282
+ ttl: z4.string().default("8h"),
283
+ refreshBuffer: z4.string().default("30m"),
284
+ setupScript: z4.string().optional(),
285
+ roles: z4.record(z4.string(), AuthRoleSchema).default({})
286
+ });
287
+ var WebSchema = z4.object({
288
+ baseUrl: z4.record(z4.string(), z4.string().url()).refine((m) => Object.keys(m).length > 0, {
289
+ message: "baseUrl must have at least one environment"
290
+ }),
291
+ defaultEnv: z4.string(),
292
+ auth: AuthSchema.prefault({}),
293
+ testData: z4.object({
294
+ users: z4.record(z4.string(), z4.object({ fromAuth: z4.string() })).default({})
295
+ }).prefault({})
296
+ }).refine((w) => w.baseUrl[w.defaultEnv] !== undefined, {
297
+ message: "defaultEnv must exist in baseUrl map",
298
+ path: ["defaultEnv"]
299
+ });
300
+ var JiraSchema = z4.object({
301
+ baseUrl: z4.string().url(),
302
+ projectKeys: z4.array(z4.string().min(1)).min(1),
303
+ fields: z4.object({
304
+ story: z4.string().min(1),
305
+ acceptanceCriteria: z4.string().optional(),
306
+ attachments: z4.string().default("attachment")
307
+ })
308
+ });
309
+ var AISchema = z4.object({
310
+ livePageSnapshot: z4.boolean().default(true),
311
+ confidenceThreshold: z4.enum(["low", "medium", "high"]).default("medium"),
312
+ maxRetries: z4.object({
313
+ typecheck: z4.number().int().min(0).max(5).default(2),
314
+ lint: z4.number().int().min(0).max(5).default(2),
315
+ validateFeature: z4.number().int().min(0).max(5).default(2)
316
+ }).prefault({})
317
+ }).prefault({});
318
+ var ReportingSchema = z4.object({
319
+ language: z4.enum(["en", "vi"]).default("en"),
320
+ postToJira: z4.boolean().default(true),
321
+ transition: z4.object({
322
+ onPass: z4.string().nullable().default(null),
323
+ onFail: z4.string().nullable().default(null)
324
+ }).prefault({}),
325
+ artifactLinks: z4.enum(["git", "local"]).default("git")
326
+ }).prefault({});
327
+ var XeraConfigSchema = z4.object({
328
+ jira: JiraSchema,
329
+ web: WebSchema,
330
+ ai: AISchema,
331
+ reporting: ReportingSchema,
332
+ adapters: z4.array(z4.string().min(1)).min(1).default(["web"])
333
+ });
334
+
335
+ // src/config/load.ts
336
+ async function loadConfig(cwd) {
337
+ const path = join3(cwd, "xera.config.ts");
338
+ if (!existsSync5(path)) {
339
+ throw new Error(`xera.config.ts not found in ${cwd}`);
340
+ }
341
+ const mod = await import(pathToFileURL(path).href);
342
+ const raw = mod.default ?? mod;
343
+ return XeraConfigSchema.parse(raw);
282
344
  }
283
345
  // src/jira/mcp-backend.ts
284
- import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
346
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
285
347
  import { tmpdir } from "os";
286
- import { join as join3 } from "path";
348
+ import { join as join4 } from "path";
287
349
  var MCP_ENV = "XERA_MCP_JIRA";
288
350
  async function createMcpBackend(_baseUrl) {
289
351
  if (process.env[MCP_ENV] !== "1")
290
352
  return null;
291
- const tmpDir = join3(tmpdir(), "xera-mcp");
292
- mkdirSync5(tmpDir, { recursive: true });
353
+ const tmpDir = join4(tmpdir(), "xera-mcp");
354
+ mkdirSync4(tmpDir, { recursive: true });
293
355
  return {
294
356
  backend: "mcp",
295
357
  async fetchTicket(key, _fields) {
296
- const cachePath = join3(tmpDir, `${key}.json`);
297
- if (!existsSync7(cachePath)) {
358
+ const cachePath = join4(tmpDir, `${key}.json`);
359
+ if (!existsSync6(cachePath)) {
298
360
  throw new Error(`MCP-mode fetch requires the skill to first call mcp__atlassian__getJiraIssue and write ${cachePath}. ` + `If you are running this directly, unset ${MCP_ENV} to use REST.`);
299
361
  }
300
- const parsed = JSON.parse(readFileSync6(cachePath, "utf8"));
362
+ const parsed = JSON.parse(readFileSync5(cachePath, "utf8"));
301
363
  return parsed;
302
364
  },
303
365
  async postComment(key, body) {
304
- const outPath = join3(tmpDir, `${key}.comment.json`);
366
+ const outPath = join4(tmpDir, `${key}.comment.json`);
305
367
  writeFileSync4(outPath, JSON.stringify({ key, body }));
306
368
  return { id: "mcp-pending" };
307
369
  },
308
370
  async transitionStatus(key, statusName) {
309
- const outPath = join3(tmpDir, `${key}.transition.json`);
371
+ const outPath = join4(tmpDir, `${key}.transition.json`);
310
372
  writeFileSync4(outPath, JSON.stringify({ key, statusName }));
311
373
  },
312
374
  async listFields(_sampleKey) {
@@ -444,111 +506,77 @@ async function withRetry(fn, opts) {
444
506
  }
445
507
  throw lastErr;
446
508
  }
447
- // src/auth/encrypt.ts
448
- import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
449
- var ALGO = "aes-256-gcm";
450
- var KEY_LEN = 32;
451
- var IV_LEN = 12;
452
- var TAG_LEN = 16;
453
- var VERSION = "v1";
454
- function generateKey() {
455
- return randomBytes(KEY_LEN).toString("hex");
456
- }
457
- function keyToBuf(key) {
458
- const buf = Buffer.from(key, "hex");
459
- if (buf.length !== KEY_LEN)
460
- throw new Error(`Key must be ${KEY_LEN} bytes (got ${buf.length})`);
461
- return buf;
462
- }
463
- function encrypt(plaintext, keyHex) {
464
- const key = keyToBuf(keyHex);
465
- const iv = randomBytes(IV_LEN);
466
- const cipher = createCipheriv(ALGO, key, iv);
467
- const ct = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
468
- const tag = cipher.getAuthTag();
469
- return `${VERSION}:${iv.toString("base64")}:${tag.toString("base64")}:${ct.toString("base64")}`;
470
- }
471
- function decrypt(ciphertext, keyHex) {
472
- const [version, ivB64, tagB64, ctB64] = ciphertext.split(":");
473
- if (version !== VERSION)
474
- throw new Error(`Unsupported ciphertext version: ${version}`);
475
- if (!ivB64 || !tagB64 || !ctB64)
476
- throw new Error("Malformed ciphertext");
477
- const key = keyToBuf(keyHex);
478
- const iv = Buffer.from(ivB64, "base64");
479
- const tag = Buffer.from(tagB64, "base64");
480
- const ct = Buffer.from(ctB64, "base64");
481
- if (tag.length !== TAG_LEN)
482
- throw new Error("Bad auth tag length");
483
- const decipher = createDecipheriv(ALGO, key, iv);
484
- decipher.setAuthTag(tag);
485
- return Buffer.concat([decipher.update(ct), decipher.final()]).toString("utf8");
486
- }
487
- // src/auth/key.ts
488
- var AUTH_KEY_ENV = "XERA_AUTH_KEY";
489
- function resolveAuthKey() {
490
- const key = process.env[AUTH_KEY_ENV];
491
- if (!key) {
492
- throw new Error(`${AUTH_KEY_ENV} not set. It is auto-generated by \`xera init\` and saved to .env. If you deleted .env, regenerate it by running \`xera init --update\` \u2014 note that any cached auth state will be invalidated.`);
493
- }
494
- if (!/^[0-9a-f]{64}$/i.test(key)) {
495
- throw new Error(`${AUTH_KEY_ENV} must be a 64-character hex string (32 bytes).`);
509
+ // src/lock/file-lock.ts
510
+ import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, unlinkSync, writeFileSync as writeFileSync5 } from "fs";
511
+ import { hostname } from "os";
512
+ import { dirname as dirname3 } from "path";
513
+ function acquireLock(path, runId) {
514
+ if (existsSync7(path))
515
+ return false;
516
+ mkdirSync5(dirname3(path), { recursive: true });
517
+ const data = {
518
+ pid: process.pid,
519
+ hostname: hostname(),
520
+ started_at: new Date().toISOString(),
521
+ run_id: runId
522
+ };
523
+ try {
524
+ writeFileSync5(path, JSON.stringify(data), { flag: "wx" });
525
+ return true;
526
+ } catch {
527
+ return false;
496
528
  }
497
- return key;
498
529
  }
499
- // src/auth/state.ts
500
- import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
501
- import { join as join4 } from "path";
502
- import { z as z4 } from "zod";
503
- var AuthStateEntrySchema = z4.object({
504
- role: z4.string(),
505
- strategy: z4.enum(["storageState", "apiToken"]),
506
- created_at: z4.string(),
507
- expires_at: z4.string(),
508
- payload: z4.record(z4.string(), z4.unknown())
509
- });
510
- function pathFor(authDir, role) {
511
- return join4(authDir, `${role}.json`);
512
- }
513
- function writeAuthState(authDir, entry) {
514
- mkdirSync6(authDir, { recursive: true });
515
- const ct = encrypt(JSON.stringify(entry), resolveAuthKey());
516
- writeFileSync5(pathFor(authDir, entry.role), ct);
530
+ function releaseLock(path) {
531
+ if (existsSync7(path))
532
+ unlinkSync(path);
517
533
  }
518
- function readAuthState(authDir, role) {
519
- const p = pathFor(authDir, role);
520
- if (!existsSync8(p))
534
+ function readLock(path) {
535
+ if (!existsSync7(path))
521
536
  return null;
522
- const txt = readFileSync7(p, "utf8");
523
- const plain = decrypt(txt, resolveAuthKey());
524
- return AuthStateEntrySchema.parse(JSON.parse(plain));
525
- }
526
- // src/auth/refresh.ts
527
- var RE = /^(\d+)([hms])$/;
528
- function parseDuration(d) {
529
- const m = RE.exec(d);
530
- if (!m)
531
- throw new Error(`Bad duration "${d}" \u2014 expected e.g. "8h", "30m", "45s"`);
532
- const n = Number(m[1]);
533
- const unit = m[2];
534
- if (unit === "h")
535
- return n * 3600 * 1000;
536
- if (unit === "m")
537
- return n * 60 * 1000;
538
- return n * 1000;
537
+ return JSON.parse(readFileSync6(path, "utf8"));
539
538
  }
540
- function needsRefresh(entry, policy, now = new Date) {
541
- if (!entry)
542
- return true;
543
- const ttlMs = parseDuration(policy.ttl);
544
- const bufMs = parseDuration(policy.refreshBuffer);
545
- const createdAt = new Date(entry.created_at).getTime();
546
- if (now.getTime() - createdAt > ttlMs)
539
+ function isLockStale(path) {
540
+ const lock = readLock(path);
541
+ if (!lock)
547
542
  return true;
548
- const expiresAt = new Date(entry.expires_at).getTime();
549
- if (expiresAt - now.getTime() < bufMs)
543
+ if (lock.hostname !== hostname()) {
544
+ return false;
545
+ }
546
+ try {
547
+ process.kill(lock.pid, 0);
548
+ return false;
549
+ } catch {
550
550
  return true;
551
- return false;
551
+ }
552
+ }
553
+ function forceUnlock(path) {
554
+ releaseLock(path);
555
+ }
556
+ // src/logging/ndjson-logger.ts
557
+ import { appendFileSync, existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync7 } from "fs";
558
+ import { dirname as dirname4 } from "path";
559
+
560
+ class NdjsonLogger {
561
+ path;
562
+ constructor(path) {
563
+ this.path = path;
564
+ mkdirSync6(dirname4(path), { recursive: true });
565
+ }
566
+ log(payload) {
567
+ const entry = { ts: new Date().toISOString(), ...payload };
568
+ appendFileSync(this.path, `${JSON.stringify(entry)}
569
+ `);
570
+ }
571
+ static readAll(path) {
572
+ if (!existsSync8(path))
573
+ return [];
574
+ const txt = readFileSync7(path, "utf8").trim();
575
+ if (!txt)
576
+ return [];
577
+ return txt.split(`
578
+ `).map((line) => JSON.parse(line));
579
+ }
552
580
  }
553
581
 
554
582
  // src/index.ts