@starterculture/devkeep-actions 0.4.1 → 0.5.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.
@@ -127,6 +127,157 @@ function defaultModelFor(tier) {
127
127
  return current.id;
128
128
  }
129
129
 
130
+ // ../../modules/core/dist/redactSecrets.js
131
+ var REDACTED = "[REDACTED]";
132
+ var KNOWN_SECRET_PATTERNS = [
133
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
134
+ /gh[pousr]_[A-Za-z0-9]{36,}/g,
135
+ /github_pat_[A-Za-z0-9_]{20,}/g,
136
+ /sk-ant-[A-Za-z0-9_-]{20,}/g,
137
+ /sk-[A-Za-z0-9]{20,}/g,
138
+ /AKIA[0-9A-Z]{16}/g,
139
+ /xox[baprs]-[A-Za-z0-9-]{10,}/g,
140
+ /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g
141
+ ];
142
+ var SECRET_KEYWORD = "(?:API[_-]?KEY|SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE[_-]?KEY|CREDENTIAL)";
143
+ var ASSIGNMENT_PATTERN = new RegExp(`((?:[A-Za-z0-9]+[_-])*${SECRET_KEYWORD}(?:[_-][A-Za-z0-9]+)*\\s*[:=]\\s*)(["']?)([^"'\`\\s]+)\\2`, "gi");
144
+ function redactText(text) {
145
+ let result = text;
146
+ for (const pattern of KNOWN_SECRET_PATTERNS) {
147
+ result = result.replace(pattern, REDACTED);
148
+ }
149
+ result = result.replace(ASSIGNMENT_PATTERN, (_match, prefix, quote) => `${prefix}${quote}${REDACTED}${quote}`);
150
+ return result;
151
+ }
152
+
153
+ // ../../modules/core/dist/ciHealth.js
154
+ var EMPTY_ACTION_HEALTH = {
155
+ lastSuccessAt: null,
156
+ lastFailureAt: null,
157
+ lastFailureMessage: null,
158
+ lastFailureRunUrl: null,
159
+ consecutiveFailures: 0
160
+ };
161
+ var MAX_MESSAGE_CHARS = 2e3;
162
+ function healthPath(projectName) {
163
+ return `projects/${projectName}/health.json`;
164
+ }
165
+ function describeError(error) {
166
+ const raw = error instanceof Error ? `${error.name}: ${error.message}` : typeof error === "string" ? error : JSON.stringify(error);
167
+ const text = raw ?? "Unknown error";
168
+ return redactText(text.length > MAX_MESSAGE_CHARS ? `${text.slice(0, MAX_MESSAGE_CHARS)}\u2026` : text);
169
+ }
170
+ function applySuccess(current, source, now) {
171
+ const previous = current[source] ?? EMPTY_ACTION_HEALTH;
172
+ return {
173
+ ...current,
174
+ // The failure details are deliberately kept, not cleared: "it failed
175
+ // on Tuesday and has worked since" is worth being able to read.
176
+ [source]: { ...previous, lastSuccessAt: now.toISOString(), consecutiveFailures: 0 }
177
+ };
178
+ }
179
+ function applyFailure(current, source, error, runUrl, now) {
180
+ const previous = current[source] ?? EMPTY_ACTION_HEALTH;
181
+ return {
182
+ ...current,
183
+ [source]: {
184
+ ...previous,
185
+ lastFailureAt: now.toISOString(),
186
+ lastFailureMessage: describeError(error),
187
+ lastFailureRunUrl: runUrl,
188
+ consecutiveFailures: previous.consecutiveFailures + 1
189
+ }
190
+ };
191
+ }
192
+ async function getHealthFile(octokit, { owner, repo, projectName }) {
193
+ try {
194
+ const { data } = await octokit.rest.repos.getContent({ owner, repo, path: healthPath(projectName) });
195
+ if (Array.isArray(data) || data.type !== "file") {
196
+ return { health: {} };
197
+ }
198
+ return { health: JSON.parse(Buffer.from(data.content, "base64").toString("utf-8")), sha: data.sha };
199
+ } catch {
200
+ return { health: {} };
201
+ }
202
+ }
203
+ async function writeHealth(octokit, params, update, message) {
204
+ const { owner, repo, projectName, branch } = params;
205
+ await retryOnConflict(async () => {
206
+ const { health, sha } = await getHealthFile(octokit, params);
207
+ await octokit.rest.repos.createOrUpdateFileContents({
208
+ owner,
209
+ repo,
210
+ path: healthPath(projectName),
211
+ message,
212
+ content: Buffer.from(JSON.stringify(update(health), null, 2), "utf-8").toString("base64"),
213
+ branch,
214
+ sha
215
+ });
216
+ });
217
+ }
218
+ async function recordSuccess(octokit, params, source, now = /* @__PURE__ */ new Date()) {
219
+ await writeHealth(octokit, params, (h) => applySuccess(h, source, now), `Record ${source} success: ${params.projectName}`);
220
+ }
221
+ async function recordFailure(octokit, params, source, error, runUrl, now = /* @__PURE__ */ new Date()) {
222
+ await writeHealth(octokit, params, (h) => applyFailure(h, source, error, runUrl, now), `Record ${source} failure: ${params.projectName}`);
223
+ }
224
+ function currentRunUrl(env) {
225
+ const { GITHUB_SERVER_URL, GITHUB_REPOSITORY, GITHUB_RUN_ID } = env;
226
+ if (!GITHUB_SERVER_URL || !GITHUB_REPOSITORY || !GITHUB_RUN_ID) {
227
+ return null;
228
+ }
229
+ return `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}`;
230
+ }
231
+
232
+ // ../../modules/core/dist/ciEntrypoint.js
233
+ var VAULT_BRANCH = "main";
234
+ function healthParams(env) {
235
+ const projectName = env.PROJECT_NAME;
236
+ const vaultRepo = env.VAULT_REPO;
237
+ const token = env.DEVKEEP_GITHUB_TOKEN;
238
+ if (!projectName || !vaultRepo || !token) {
239
+ return null;
240
+ }
241
+ const [owner, repo] = vaultRepo.split("/");
242
+ if (!owner || !repo) {
243
+ return null;
244
+ }
245
+ return { owner, repo, projectName, branch: VAULT_BRANCH };
246
+ }
247
+ var defaultDeps = {
248
+ env: process.env,
249
+ createGithubClient,
250
+ recordSuccess,
251
+ recordFailure,
252
+ logError: console.error,
253
+ exit: (code) => process.exit(code)
254
+ };
255
+ async function runCiEntrypoint(source, main2, deps = defaultDeps) {
256
+ const params = healthParams(deps.env);
257
+ const octokit = params ? deps.createGithubClient(deps.env.DEVKEEP_GITHUB_TOKEN) : null;
258
+ try {
259
+ await main2();
260
+ } catch (error) {
261
+ deps.logError(error);
262
+ if (params && octokit) {
263
+ try {
264
+ await deps.recordFailure(octokit, params, source, error, currentRunUrl(deps.env));
265
+ } catch (recordingError) {
266
+ deps.logError(`Could not record the failure to the vault: ${String(recordingError)}`);
267
+ }
268
+ }
269
+ deps.exit(1);
270
+ return;
271
+ }
272
+ if (params && octokit) {
273
+ try {
274
+ await deps.recordSuccess(octokit, params, source);
275
+ } catch (recordingError) {
276
+ deps.logError(`Ran successfully, but could not record it to the vault: ${String(recordingError)}`);
277
+ }
278
+ }
279
+ }
280
+
130
281
  // ../../modules/devlore/dist/ciUsage.js
131
282
  var EMPTY_PROJECT_USAGE = {
132
283
  draftLogEntry: ZERO_USAGE,
@@ -429,14 +580,14 @@ function requireEnv(env, name) {
429
580
  }
430
581
  return value;
431
582
  }
432
- var defaultDeps = {
583
+ var defaultDeps2 = {
433
584
  env: process.env,
434
585
  consolidateRelease,
435
586
  createGithubClient,
436
587
  createClaudeClient,
437
588
  log: console.log
438
589
  };
439
- async function main(deps = defaultDeps) {
590
+ async function main(deps = defaultDeps2) {
440
591
  const { env } = deps;
441
592
  const projectName = requireEnv(env, "PROJECT_NAME");
442
593
  const [projectOwner, projectRepoName] = requireEnv(env, "PROJECT_REPO").split("/");
@@ -459,10 +610,7 @@ async function main(deps = defaultDeps) {
459
610
  deps.log(`Opened consolidation PR #${result.prNumber}: ${result.prUrl}`);
460
611
  }
461
612
  if (import.meta.url === `file://${process.argv[1]}`) {
462
- main().catch((error) => {
463
- console.error(error);
464
- process.exit(1);
465
- });
613
+ void runCiEntrypoint("consolidateRelease", main);
466
614
  }
467
615
  export {
468
616
  main,
@@ -24,6 +24,157 @@ async function retryOnConflict(attempt, maxAttempts = 3) {
24
24
  }
25
25
  }
26
26
 
27
+ // ../../modules/core/dist/redactSecrets.js
28
+ var REDACTED = "[REDACTED]";
29
+ var KNOWN_SECRET_PATTERNS = [
30
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
31
+ /gh[pousr]_[A-Za-z0-9]{36,}/g,
32
+ /github_pat_[A-Za-z0-9_]{20,}/g,
33
+ /sk-ant-[A-Za-z0-9_-]{20,}/g,
34
+ /sk-[A-Za-z0-9]{20,}/g,
35
+ /AKIA[0-9A-Z]{16}/g,
36
+ /xox[baprs]-[A-Za-z0-9-]{10,}/g,
37
+ /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g
38
+ ];
39
+ var SECRET_KEYWORD = "(?:API[_-]?KEY|SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE[_-]?KEY|CREDENTIAL)";
40
+ var ASSIGNMENT_PATTERN = new RegExp(`((?:[A-Za-z0-9]+[_-])*${SECRET_KEYWORD}(?:[_-][A-Za-z0-9]+)*\\s*[:=]\\s*)(["']?)([^"'\`\\s]+)\\2`, "gi");
41
+ function redactText(text) {
42
+ let result = text;
43
+ for (const pattern of KNOWN_SECRET_PATTERNS) {
44
+ result = result.replace(pattern, REDACTED);
45
+ }
46
+ result = result.replace(ASSIGNMENT_PATTERN, (_match, prefix, quote) => `${prefix}${quote}${REDACTED}${quote}`);
47
+ return result;
48
+ }
49
+
50
+ // ../../modules/core/dist/ciHealth.js
51
+ var EMPTY_ACTION_HEALTH = {
52
+ lastSuccessAt: null,
53
+ lastFailureAt: null,
54
+ lastFailureMessage: null,
55
+ lastFailureRunUrl: null,
56
+ consecutiveFailures: 0
57
+ };
58
+ var MAX_MESSAGE_CHARS = 2e3;
59
+ function healthPath(projectName) {
60
+ return `projects/${projectName}/health.json`;
61
+ }
62
+ function describeError(error) {
63
+ const raw = error instanceof Error ? `${error.name}: ${error.message}` : typeof error === "string" ? error : JSON.stringify(error);
64
+ const text = raw ?? "Unknown error";
65
+ return redactText(text.length > MAX_MESSAGE_CHARS ? `${text.slice(0, MAX_MESSAGE_CHARS)}\u2026` : text);
66
+ }
67
+ function applySuccess(current, source, now) {
68
+ const previous = current[source] ?? EMPTY_ACTION_HEALTH;
69
+ return {
70
+ ...current,
71
+ // The failure details are deliberately kept, not cleared: "it failed
72
+ // on Tuesday and has worked since" is worth being able to read.
73
+ [source]: { ...previous, lastSuccessAt: now.toISOString(), consecutiveFailures: 0 }
74
+ };
75
+ }
76
+ function applyFailure(current, source, error, runUrl, now) {
77
+ const previous = current[source] ?? EMPTY_ACTION_HEALTH;
78
+ return {
79
+ ...current,
80
+ [source]: {
81
+ ...previous,
82
+ lastFailureAt: now.toISOString(),
83
+ lastFailureMessage: describeError(error),
84
+ lastFailureRunUrl: runUrl,
85
+ consecutiveFailures: previous.consecutiveFailures + 1
86
+ }
87
+ };
88
+ }
89
+ async function getHealthFile(octokit, { owner, repo, projectName }) {
90
+ try {
91
+ const { data } = await octokit.rest.repos.getContent({ owner, repo, path: healthPath(projectName) });
92
+ if (Array.isArray(data) || data.type !== "file") {
93
+ return { health: {} };
94
+ }
95
+ return { health: JSON.parse(Buffer.from(data.content, "base64").toString("utf-8")), sha: data.sha };
96
+ } catch {
97
+ return { health: {} };
98
+ }
99
+ }
100
+ async function writeHealth(octokit, params, update, message) {
101
+ const { owner, repo, projectName, branch } = params;
102
+ await retryOnConflict(async () => {
103
+ const { health, sha } = await getHealthFile(octokit, params);
104
+ await octokit.rest.repos.createOrUpdateFileContents({
105
+ owner,
106
+ repo,
107
+ path: healthPath(projectName),
108
+ message,
109
+ content: Buffer.from(JSON.stringify(update(health), null, 2), "utf-8").toString("base64"),
110
+ branch,
111
+ sha
112
+ });
113
+ });
114
+ }
115
+ async function recordSuccess(octokit, params, source, now = /* @__PURE__ */ new Date()) {
116
+ await writeHealth(octokit, params, (h) => applySuccess(h, source, now), `Record ${source} success: ${params.projectName}`);
117
+ }
118
+ async function recordFailure(octokit, params, source, error, runUrl, now = /* @__PURE__ */ new Date()) {
119
+ await writeHealth(octokit, params, (h) => applyFailure(h, source, error, runUrl, now), `Record ${source} failure: ${params.projectName}`);
120
+ }
121
+ function currentRunUrl(env) {
122
+ const { GITHUB_SERVER_URL, GITHUB_REPOSITORY, GITHUB_RUN_ID } = env;
123
+ if (!GITHUB_SERVER_URL || !GITHUB_REPOSITORY || !GITHUB_RUN_ID) {
124
+ return null;
125
+ }
126
+ return `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}`;
127
+ }
128
+
129
+ // ../../modules/core/dist/ciEntrypoint.js
130
+ var VAULT_BRANCH = "main";
131
+ function healthParams(env) {
132
+ const projectName = env.PROJECT_NAME;
133
+ const vaultRepo = env.VAULT_REPO;
134
+ const token = env.DEVKEEP_GITHUB_TOKEN;
135
+ if (!projectName || !vaultRepo || !token) {
136
+ return null;
137
+ }
138
+ const [owner, repo] = vaultRepo.split("/");
139
+ if (!owner || !repo) {
140
+ return null;
141
+ }
142
+ return { owner, repo, projectName, branch: VAULT_BRANCH };
143
+ }
144
+ var defaultDeps = {
145
+ env: process.env,
146
+ createGithubClient,
147
+ recordSuccess,
148
+ recordFailure,
149
+ logError: console.error,
150
+ exit: (code) => process.exit(code)
151
+ };
152
+ async function runCiEntrypoint(source, main2, deps = defaultDeps) {
153
+ const params = healthParams(deps.env);
154
+ const octokit = params ? deps.createGithubClient(deps.env.DEVKEEP_GITHUB_TOKEN) : null;
155
+ try {
156
+ await main2();
157
+ } catch (error) {
158
+ deps.logError(error);
159
+ if (params && octokit) {
160
+ try {
161
+ await deps.recordFailure(octokit, params, source, error, currentRunUrl(deps.env));
162
+ } catch (recordingError) {
163
+ deps.logError(`Could not record the failure to the vault: ${String(recordingError)}`);
164
+ }
165
+ }
166
+ deps.exit(1);
167
+ return;
168
+ }
169
+ if (params && octokit) {
170
+ try {
171
+ await deps.recordSuccess(octokit, params, source);
172
+ } catch (recordingError) {
173
+ deps.logError(`Ran successfully, but could not record it to the vault: ${String(recordingError)}`);
174
+ }
175
+ }
176
+ }
177
+
27
178
  // ../../modules/devlore/dist/diff.js
28
179
  function toDiffFile(file) {
29
180
  return {
@@ -70,8 +221,8 @@ var SECRET_PREFIX_PATTERNS = [
70
221
  // JWT-shaped
71
222
  ];
72
223
  var PEM_BLOCK_PATTERN = /-----BEGIN [A-Z ]*PRIVATE KEY-----/;
73
- var SECRET_KEYWORD = "(?:API[_-]?KEY|SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE[_-]?KEY|CREDENTIAL)";
74
- var SECRET_ASSIGNMENT_PATTERN = new RegExp(`(?:[A-Za-z0-9]+[_-])*${SECRET_KEYWORD}(?:[_-][A-Za-z0-9]+)*\\s*[:=]\\s*["']([^"'\`]+)["']`, "i");
224
+ var SECRET_KEYWORD2 = "(?:API[_-]?KEY|SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE[_-]?KEY|CREDENTIAL)";
225
+ var SECRET_ASSIGNMENT_PATTERN = new RegExp(`(?:[A-Za-z0-9]+[_-])*${SECRET_KEYWORD2}(?:[_-][A-Za-z0-9]+)*\\s*[:=]\\s*["']([^"'\`]+)["']`, "i");
75
226
 
76
227
  // ../../modules/devcrypt/dist/candidateScan.js
77
228
  function addedLines(patch) {
@@ -332,13 +483,13 @@ function requireEnv(env, name) {
332
483
  }
333
484
  return value;
334
485
  }
335
- var defaultDeps = {
486
+ var defaultDeps2 = {
336
487
  env: process.env,
337
488
  scanCryptingCandidates,
338
489
  createGithubClient,
339
490
  log: console.log
340
491
  };
341
- async function main(deps = defaultDeps) {
492
+ async function main(deps = defaultDeps2) {
342
493
  const { env } = deps;
343
494
  const [projectOwner, projectRepoName] = requireEnv(env, "PROJECT_REPO").split("/");
344
495
  const [vaultOwner, vaultRepo] = requireEnv(env, "VAULT_REPO").split("/");
@@ -360,10 +511,7 @@ async function main(deps = defaultDeps) {
360
511
  deps.log(`Flagged ${result.candidatesFlagged} crypting candidate(s)`);
361
512
  }
362
513
  if (import.meta.url === `file://${process.argv[1]}`) {
363
- main().catch((error) => {
364
- console.error(error);
365
- process.exit(1);
366
- });
514
+ void runCiEntrypoint("cryptingCandidatesScan", main);
367
515
  }
368
516
  export {
369
517
  main,
@@ -127,6 +127,157 @@ function defaultModelFor(tier) {
127
127
  return current.id;
128
128
  }
129
129
 
130
+ // ../../modules/core/dist/redactSecrets.js
131
+ var REDACTED = "[REDACTED]";
132
+ var KNOWN_SECRET_PATTERNS = [
133
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
134
+ /gh[pousr]_[A-Za-z0-9]{36,}/g,
135
+ /github_pat_[A-Za-z0-9_]{20,}/g,
136
+ /sk-ant-[A-Za-z0-9_-]{20,}/g,
137
+ /sk-[A-Za-z0-9]{20,}/g,
138
+ /AKIA[0-9A-Z]{16}/g,
139
+ /xox[baprs]-[A-Za-z0-9-]{10,}/g,
140
+ /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g
141
+ ];
142
+ var SECRET_KEYWORD = "(?:API[_-]?KEY|SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE[_-]?KEY|CREDENTIAL)";
143
+ var ASSIGNMENT_PATTERN = new RegExp(`((?:[A-Za-z0-9]+[_-])*${SECRET_KEYWORD}(?:[_-][A-Za-z0-9]+)*\\s*[:=]\\s*)(["']?)([^"'\`\\s]+)\\2`, "gi");
144
+ function redactText(text) {
145
+ let result = text;
146
+ for (const pattern of KNOWN_SECRET_PATTERNS) {
147
+ result = result.replace(pattern, REDACTED);
148
+ }
149
+ result = result.replace(ASSIGNMENT_PATTERN, (_match, prefix, quote) => `${prefix}${quote}${REDACTED}${quote}`);
150
+ return result;
151
+ }
152
+
153
+ // ../../modules/core/dist/ciHealth.js
154
+ var EMPTY_ACTION_HEALTH = {
155
+ lastSuccessAt: null,
156
+ lastFailureAt: null,
157
+ lastFailureMessage: null,
158
+ lastFailureRunUrl: null,
159
+ consecutiveFailures: 0
160
+ };
161
+ var MAX_MESSAGE_CHARS = 2e3;
162
+ function healthPath(projectName) {
163
+ return `projects/${projectName}/health.json`;
164
+ }
165
+ function describeError(error) {
166
+ const raw = error instanceof Error ? `${error.name}: ${error.message}` : typeof error === "string" ? error : JSON.stringify(error);
167
+ const text = raw ?? "Unknown error";
168
+ return redactText(text.length > MAX_MESSAGE_CHARS ? `${text.slice(0, MAX_MESSAGE_CHARS)}\u2026` : text);
169
+ }
170
+ function applySuccess(current, source, now) {
171
+ const previous = current[source] ?? EMPTY_ACTION_HEALTH;
172
+ return {
173
+ ...current,
174
+ // The failure details are deliberately kept, not cleared: "it failed
175
+ // on Tuesday and has worked since" is worth being able to read.
176
+ [source]: { ...previous, lastSuccessAt: now.toISOString(), consecutiveFailures: 0 }
177
+ };
178
+ }
179
+ function applyFailure(current, source, error, runUrl, now) {
180
+ const previous = current[source] ?? EMPTY_ACTION_HEALTH;
181
+ return {
182
+ ...current,
183
+ [source]: {
184
+ ...previous,
185
+ lastFailureAt: now.toISOString(),
186
+ lastFailureMessage: describeError(error),
187
+ lastFailureRunUrl: runUrl,
188
+ consecutiveFailures: previous.consecutiveFailures + 1
189
+ }
190
+ };
191
+ }
192
+ async function getHealthFile(octokit, { owner, repo, projectName }) {
193
+ try {
194
+ const { data } = await octokit.rest.repos.getContent({ owner, repo, path: healthPath(projectName) });
195
+ if (Array.isArray(data) || data.type !== "file") {
196
+ return { health: {} };
197
+ }
198
+ return { health: JSON.parse(Buffer.from(data.content, "base64").toString("utf-8")), sha: data.sha };
199
+ } catch {
200
+ return { health: {} };
201
+ }
202
+ }
203
+ async function writeHealth(octokit, params, update, message) {
204
+ const { owner, repo, projectName, branch } = params;
205
+ await retryOnConflict(async () => {
206
+ const { health, sha } = await getHealthFile(octokit, params);
207
+ await octokit.rest.repos.createOrUpdateFileContents({
208
+ owner,
209
+ repo,
210
+ path: healthPath(projectName),
211
+ message,
212
+ content: Buffer.from(JSON.stringify(update(health), null, 2), "utf-8").toString("base64"),
213
+ branch,
214
+ sha
215
+ });
216
+ });
217
+ }
218
+ async function recordSuccess(octokit, params, source, now = /* @__PURE__ */ new Date()) {
219
+ await writeHealth(octokit, params, (h) => applySuccess(h, source, now), `Record ${source} success: ${params.projectName}`);
220
+ }
221
+ async function recordFailure(octokit, params, source, error, runUrl, now = /* @__PURE__ */ new Date()) {
222
+ await writeHealth(octokit, params, (h) => applyFailure(h, source, error, runUrl, now), `Record ${source} failure: ${params.projectName}`);
223
+ }
224
+ function currentRunUrl(env) {
225
+ const { GITHUB_SERVER_URL, GITHUB_REPOSITORY, GITHUB_RUN_ID } = env;
226
+ if (!GITHUB_SERVER_URL || !GITHUB_REPOSITORY || !GITHUB_RUN_ID) {
227
+ return null;
228
+ }
229
+ return `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}`;
230
+ }
231
+
232
+ // ../../modules/core/dist/ciEntrypoint.js
233
+ var VAULT_BRANCH = "main";
234
+ function healthParams(env) {
235
+ const projectName = env.PROJECT_NAME;
236
+ const vaultRepo = env.VAULT_REPO;
237
+ const token = env.DEVKEEP_GITHUB_TOKEN;
238
+ if (!projectName || !vaultRepo || !token) {
239
+ return null;
240
+ }
241
+ const [owner, repo] = vaultRepo.split("/");
242
+ if (!owner || !repo) {
243
+ return null;
244
+ }
245
+ return { owner, repo, projectName, branch: VAULT_BRANCH };
246
+ }
247
+ var defaultDeps = {
248
+ env: process.env,
249
+ createGithubClient,
250
+ recordSuccess,
251
+ recordFailure,
252
+ logError: console.error,
253
+ exit: (code) => process.exit(code)
254
+ };
255
+ async function runCiEntrypoint(source, main2, deps = defaultDeps) {
256
+ const params = healthParams(deps.env);
257
+ const octokit = params ? deps.createGithubClient(deps.env.DEVKEEP_GITHUB_TOKEN) : null;
258
+ try {
259
+ await main2();
260
+ } catch (error) {
261
+ deps.logError(error);
262
+ if (params && octokit) {
263
+ try {
264
+ await deps.recordFailure(octokit, params, source, error, currentRunUrl(deps.env));
265
+ } catch (recordingError) {
266
+ deps.logError(`Could not record the failure to the vault: ${String(recordingError)}`);
267
+ }
268
+ }
269
+ deps.exit(1);
270
+ return;
271
+ }
272
+ if (params && octokit) {
273
+ try {
274
+ await deps.recordSuccess(octokit, params, source);
275
+ } catch (recordingError) {
276
+ deps.logError(`Ran successfully, but could not record it to the vault: ${String(recordingError)}`);
277
+ }
278
+ }
279
+ }
280
+
130
281
  // ../../modules/devlore/dist/ciUsage.js
131
282
  var EMPTY_PROJECT_USAGE = {
132
283
  draftLogEntry: ZERO_USAGE,
@@ -484,19 +635,6 @@ function routeDiffByModule(files, manifest) {
484
635
  }
485
636
 
486
637
  // ../../modules/devlore/dist/redact.js
487
- var REDACTED = "[REDACTED]";
488
- var KNOWN_SECRET_PATTERNS = [
489
- /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
490
- /gh[pousr]_[A-Za-z0-9]{36,}/g,
491
- /github_pat_[A-Za-z0-9_]{20,}/g,
492
- /sk-ant-[A-Za-z0-9_-]{20,}/g,
493
- /sk-[A-Za-z0-9]{20,}/g,
494
- /AKIA[0-9A-Z]{16}/g,
495
- /xox[baprs]-[A-Za-z0-9-]{10,}/g,
496
- /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g
497
- ];
498
- var SECRET_KEYWORD = "(?:API[_-]?KEY|SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE[_-]?KEY|CREDENTIAL)";
499
- var ASSIGNMENT_PATTERN = new RegExp(`((?:[A-Za-z0-9]+[_-])*${SECRET_KEYWORD}(?:[_-][A-Za-z0-9]+)*\\s*[:=]\\s*)(["']?)([^"'\`\\s]+)\\2`, "gi");
500
638
  var SENSITIVE_FILENAME_PATTERNS = [
501
639
  /(^|\/)\.env(\..+)?$/,
502
640
  /\.pem$/,
@@ -508,14 +646,6 @@ var SENSITIVE_FILENAME_PATTERNS = [
508
646
  /credentials\.json$/,
509
647
  /secrets?\.(json|ya?ml|toml)$/
510
648
  ];
511
- function redactText(text) {
512
- let result = text;
513
- for (const pattern of KNOWN_SECRET_PATTERNS) {
514
- result = result.replace(pattern, REDACTED);
515
- }
516
- result = result.replace(ASSIGNMENT_PATTERN, (_match, prefix, quote) => `${prefix}${quote}${REDACTED}${quote}`);
517
- return result;
518
- }
519
649
  function isSensitiveFilename(filename) {
520
650
  return SENSITIVE_FILENAME_PATTERNS.some((pattern) => pattern.test(filename));
521
651
  }
@@ -533,7 +663,7 @@ function redactProjectDiff(diff) {
533
663
  }
534
664
 
535
665
  // ../../modules/devlore/dist/actions/draftLogEntry.js
536
- var VAULT_BRANCH = "main";
666
+ var VAULT_BRANCH2 = "main";
537
667
  var MODEL_TIER = "balanced";
538
668
  var MAX_TOKENS = 2048;
539
669
  function logEntryPath(projectName, commitSha, moduleId) {
@@ -567,7 +697,7 @@ async function syncProductDoc(projectOctokit, vaultOctokit, { projectOwner, proj
567
697
  path: productDocPath(projectName, moduleId),
568
698
  message: `Sync PRODUCT.md: ${projectName}@${headSha.slice(0, 7)}`,
569
699
  content: Buffer.from(redactedDoc, "utf-8").toString("base64"),
570
- branch: VAULT_BRANCH,
700
+ branch: VAULT_BRANCH2,
571
701
  sha: existing.sha
572
702
  });
573
703
  });
@@ -600,7 +730,7 @@ ${entryBody}
600
730
  path: entryPath,
601
731
  message: `Draft log entry: ${projectName}@${shortSha}${moduleName ? ` (${moduleName})` : ""}`,
602
732
  content: Buffer.from(entryContent, "utf-8").toString("base64"),
603
- branch: VAULT_BRANCH
733
+ branch: VAULT_BRANCH2
604
734
  }));
605
735
  let decisionPath = null;
606
736
  if (decisionEntry) {
@@ -615,7 +745,7 @@ ${decisionEntry}
615
745
  path: decisionPath,
616
746
  message: `Draft decision entry: ${projectName}@${shortSha}${moduleName ? ` (${moduleName})` : ""}`,
617
747
  content: Buffer.from(decisionContent, "utf-8").toString("base64"),
618
- branch: VAULT_BRANCH
748
+ branch: VAULT_BRANCH2
619
749
  }));
620
750
  }
621
751
  return { moduleId, logEntryPath: entryPath, decisionEntryPath: decisionPath, usage };
@@ -668,7 +798,7 @@ async function draftLogEntry({ projectOctokit, vaultOctokit, claude, projectOwne
668
798
  });
669
799
  }
670
800
  const totalUsage = results.reduce((total, r) => addTokenUsage(total, r.usage), ZERO_USAGE);
671
- await recordUsage(vaultOctokit, { owner: vaultOwner, repo: vaultRepoName, projectName, branch: VAULT_BRANCH }, "draftLogEntry", totalUsage);
801
+ await recordUsage(vaultOctokit, { owner: vaultOwner, repo: vaultRepoName, projectName, branch: VAULT_BRANCH2 }, "draftLogEntry", totalUsage);
672
802
  return results;
673
803
  }
674
804
 
@@ -680,14 +810,14 @@ function requireEnv(env, name) {
680
810
  }
681
811
  return value;
682
812
  }
683
- var defaultDeps = {
813
+ var defaultDeps2 = {
684
814
  env: process.env,
685
815
  draftLogEntry,
686
816
  createGithubClient,
687
817
  createClaudeClient,
688
818
  log: console.log
689
819
  };
690
- async function main(deps = defaultDeps) {
820
+ async function main(deps = defaultDeps2) {
691
821
  const { env } = deps;
692
822
  const projectName = requireEnv(env, "PROJECT_NAME");
693
823
  const [projectOwner, projectRepoName] = requireEnv(env, "PROJECT_REPO").split("/");
@@ -717,10 +847,7 @@ async function main(deps = defaultDeps) {
717
847
  }
718
848
  }
719
849
  if (import.meta.url === `file://${process.argv[1]}`) {
720
- main().catch((error) => {
721
- console.error(error);
722
- process.exit(1);
723
- });
850
+ void runCiEntrypoint("draftLogEntry", main);
724
851
  }
725
852
  export {
726
853
  main,
@@ -127,6 +127,157 @@ function defaultModelFor(tier) {
127
127
  return current.id;
128
128
  }
129
129
 
130
+ // ../../modules/core/dist/redactSecrets.js
131
+ var REDACTED = "[REDACTED]";
132
+ var KNOWN_SECRET_PATTERNS = [
133
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
134
+ /gh[pousr]_[A-Za-z0-9]{36,}/g,
135
+ /github_pat_[A-Za-z0-9_]{20,}/g,
136
+ /sk-ant-[A-Za-z0-9_-]{20,}/g,
137
+ /sk-[A-Za-z0-9]{20,}/g,
138
+ /AKIA[0-9A-Z]{16}/g,
139
+ /xox[baprs]-[A-Za-z0-9-]{10,}/g,
140
+ /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g
141
+ ];
142
+ var SECRET_KEYWORD = "(?:API[_-]?KEY|SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE[_-]?KEY|CREDENTIAL)";
143
+ var ASSIGNMENT_PATTERN = new RegExp(`((?:[A-Za-z0-9]+[_-])*${SECRET_KEYWORD}(?:[_-][A-Za-z0-9]+)*\\s*[:=]\\s*)(["']?)([^"'\`\\s]+)\\2`, "gi");
144
+ function redactText(text) {
145
+ let result = text;
146
+ for (const pattern of KNOWN_SECRET_PATTERNS) {
147
+ result = result.replace(pattern, REDACTED);
148
+ }
149
+ result = result.replace(ASSIGNMENT_PATTERN, (_match, prefix, quote) => `${prefix}${quote}${REDACTED}${quote}`);
150
+ return result;
151
+ }
152
+
153
+ // ../../modules/core/dist/ciHealth.js
154
+ var EMPTY_ACTION_HEALTH = {
155
+ lastSuccessAt: null,
156
+ lastFailureAt: null,
157
+ lastFailureMessage: null,
158
+ lastFailureRunUrl: null,
159
+ consecutiveFailures: 0
160
+ };
161
+ var MAX_MESSAGE_CHARS = 2e3;
162
+ function healthPath(projectName) {
163
+ return `projects/${projectName}/health.json`;
164
+ }
165
+ function describeError(error) {
166
+ const raw = error instanceof Error ? `${error.name}: ${error.message}` : typeof error === "string" ? error : JSON.stringify(error);
167
+ const text = raw ?? "Unknown error";
168
+ return redactText(text.length > MAX_MESSAGE_CHARS ? `${text.slice(0, MAX_MESSAGE_CHARS)}\u2026` : text);
169
+ }
170
+ function applySuccess(current, source, now) {
171
+ const previous = current[source] ?? EMPTY_ACTION_HEALTH;
172
+ return {
173
+ ...current,
174
+ // The failure details are deliberately kept, not cleared: "it failed
175
+ // on Tuesday and has worked since" is worth being able to read.
176
+ [source]: { ...previous, lastSuccessAt: now.toISOString(), consecutiveFailures: 0 }
177
+ };
178
+ }
179
+ function applyFailure(current, source, error, runUrl, now) {
180
+ const previous = current[source] ?? EMPTY_ACTION_HEALTH;
181
+ return {
182
+ ...current,
183
+ [source]: {
184
+ ...previous,
185
+ lastFailureAt: now.toISOString(),
186
+ lastFailureMessage: describeError(error),
187
+ lastFailureRunUrl: runUrl,
188
+ consecutiveFailures: previous.consecutiveFailures + 1
189
+ }
190
+ };
191
+ }
192
+ async function getHealthFile(octokit, { owner, repo, projectName }) {
193
+ try {
194
+ const { data } = await octokit.rest.repos.getContent({ owner, repo, path: healthPath(projectName) });
195
+ if (Array.isArray(data) || data.type !== "file") {
196
+ return { health: {} };
197
+ }
198
+ return { health: JSON.parse(Buffer.from(data.content, "base64").toString("utf-8")), sha: data.sha };
199
+ } catch {
200
+ return { health: {} };
201
+ }
202
+ }
203
+ async function writeHealth(octokit, params, update, message) {
204
+ const { owner, repo, projectName, branch } = params;
205
+ await retryOnConflict(async () => {
206
+ const { health, sha } = await getHealthFile(octokit, params);
207
+ await octokit.rest.repos.createOrUpdateFileContents({
208
+ owner,
209
+ repo,
210
+ path: healthPath(projectName),
211
+ message,
212
+ content: Buffer.from(JSON.stringify(update(health), null, 2), "utf-8").toString("base64"),
213
+ branch,
214
+ sha
215
+ });
216
+ });
217
+ }
218
+ async function recordSuccess(octokit, params, source, now = /* @__PURE__ */ new Date()) {
219
+ await writeHealth(octokit, params, (h) => applySuccess(h, source, now), `Record ${source} success: ${params.projectName}`);
220
+ }
221
+ async function recordFailure(octokit, params, source, error, runUrl, now = /* @__PURE__ */ new Date()) {
222
+ await writeHealth(octokit, params, (h) => applyFailure(h, source, error, runUrl, now), `Record ${source} failure: ${params.projectName}`);
223
+ }
224
+ function currentRunUrl(env) {
225
+ const { GITHUB_SERVER_URL, GITHUB_REPOSITORY, GITHUB_RUN_ID } = env;
226
+ if (!GITHUB_SERVER_URL || !GITHUB_REPOSITORY || !GITHUB_RUN_ID) {
227
+ return null;
228
+ }
229
+ return `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}`;
230
+ }
231
+
232
+ // ../../modules/core/dist/ciEntrypoint.js
233
+ var VAULT_BRANCH = "main";
234
+ function healthParams(env) {
235
+ const projectName = env.PROJECT_NAME;
236
+ const vaultRepo = env.VAULT_REPO;
237
+ const token = env.DEVKEEP_GITHUB_TOKEN;
238
+ if (!projectName || !vaultRepo || !token) {
239
+ return null;
240
+ }
241
+ const [owner, repo] = vaultRepo.split("/");
242
+ if (!owner || !repo) {
243
+ return null;
244
+ }
245
+ return { owner, repo, projectName, branch: VAULT_BRANCH };
246
+ }
247
+ var defaultDeps = {
248
+ env: process.env,
249
+ createGithubClient,
250
+ recordSuccess,
251
+ recordFailure,
252
+ logError: console.error,
253
+ exit: (code) => process.exit(code)
254
+ };
255
+ async function runCiEntrypoint(source, main2, deps = defaultDeps) {
256
+ const params = healthParams(deps.env);
257
+ const octokit = params ? deps.createGithubClient(deps.env.DEVKEEP_GITHUB_TOKEN) : null;
258
+ try {
259
+ await main2();
260
+ } catch (error) {
261
+ deps.logError(error);
262
+ if (params && octokit) {
263
+ try {
264
+ await deps.recordFailure(octokit, params, source, error, currentRunUrl(deps.env));
265
+ } catch (recordingError) {
266
+ deps.logError(`Could not record the failure to the vault: ${String(recordingError)}`);
267
+ }
268
+ }
269
+ deps.exit(1);
270
+ return;
271
+ }
272
+ if (params && octokit) {
273
+ try {
274
+ await deps.recordSuccess(octokit, params, source);
275
+ } catch (recordingError) {
276
+ deps.logError(`Ran successfully, but could not record it to the vault: ${String(recordingError)}`);
277
+ }
278
+ }
279
+ }
280
+
130
281
  // ../../modules/devlore/dist/prompt.js
131
282
  function buildReleaseNotesPrompt(input) {
132
283
  return [
@@ -150,29 +301,6 @@ function buildReleaseNotesPrompt(input) {
150
301
  ].join("\n");
151
302
  }
152
303
 
153
- // ../../modules/devlore/dist/redact.js
154
- var REDACTED = "[REDACTED]";
155
- var KNOWN_SECRET_PATTERNS = [
156
- /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
157
- /gh[pousr]_[A-Za-z0-9]{36,}/g,
158
- /github_pat_[A-Za-z0-9_]{20,}/g,
159
- /sk-ant-[A-Za-z0-9_-]{20,}/g,
160
- /sk-[A-Za-z0-9]{20,}/g,
161
- /AKIA[0-9A-Z]{16}/g,
162
- /xox[baprs]-[A-Za-z0-9-]{10,}/g,
163
- /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g
164
- ];
165
- var SECRET_KEYWORD = "(?:API[_-]?KEY|SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE[_-]?KEY|CREDENTIAL)";
166
- var ASSIGNMENT_PATTERN = new RegExp(`((?:[A-Za-z0-9]+[_-])*${SECRET_KEYWORD}(?:[_-][A-Za-z0-9]+)*\\s*[:=]\\s*)(["']?)([^"'\`\\s]+)\\2`, "gi");
167
- function redactText(text) {
168
- let result = text;
169
- for (const pattern of KNOWN_SECRET_PATTERNS) {
170
- result = result.replace(pattern, REDACTED);
171
- }
172
- result = result.replace(ASSIGNMENT_PATTERN, (_match, prefix, quote) => `${prefix}${quote}${REDACTED}${quote}`);
173
- return result;
174
- }
175
-
176
304
  // ../../modules/devlore/dist/ciUsage.js
177
305
  var EMPTY_PROJECT_USAGE = {
178
306
  draftLogEntry: ZERO_USAGE,
@@ -354,7 +482,7 @@ function releaseNotesDir(projectName) {
354
482
  var MODEL_TIER = "balanced";
355
483
  var MAX_TOKENS = 2e4;
356
484
  var MAX_CONTINUATIONS = 8;
357
- var VAULT_BRANCH = "main";
485
+ var VAULT_BRANCH2 = "main";
358
486
  async function draftReleaseNotes({ projectOctokit, vaultOctokit, claude, projectOwner, projectRepoName, vaultOwner, vaultRepoName, projectName, tag }) {
359
487
  const { logEntries, previousTag } = await selectLogEntriesSincePreviousRelease({
360
488
  vaultOctokit,
@@ -386,11 +514,11 @@ ${text.trim()}
386
514
  path,
387
515
  message: `Draft release notes: ${projectName} ${tag}`,
388
516
  content: Buffer.from(content, "utf-8").toString("base64"),
389
- branch: VAULT_BRANCH,
517
+ branch: VAULT_BRANCH2,
390
518
  sha: await getExistingSha(vaultOctokit, vaultOwner, vaultRepoName, path)
391
519
  });
392
520
  if (usage.inputTokens > 0 || usage.outputTokens > 0) {
393
- await recordUsage(vaultOctokit, { owner: vaultOwner, repo: vaultRepoName, projectName, branch: VAULT_BRANCH }, "releaseNotes", usage);
521
+ await recordUsage(vaultOctokit, { owner: vaultOwner, repo: vaultRepoName, projectName, branch: VAULT_BRANCH2 }, "releaseNotes", usage);
394
522
  }
395
523
  return { path, previousTag, entryCount: logEntries.length, content, usage };
396
524
  }
@@ -411,14 +539,14 @@ function requireEnv(env, name) {
411
539
  }
412
540
  return value;
413
541
  }
414
- var defaultDeps = {
542
+ var defaultDeps2 = {
415
543
  env: process.env,
416
544
  draftReleaseNotes,
417
545
  createGithubClient,
418
546
  createClaudeClient,
419
547
  log: console.log
420
548
  };
421
- async function main(deps = defaultDeps) {
549
+ async function main(deps = defaultDeps2) {
422
550
  const { env } = deps;
423
551
  const projectName = requireEnv(env, "PROJECT_NAME");
424
552
  const [projectOwner, projectRepoName] = requireEnv(env, "PROJECT_REPO").split("/");
@@ -440,10 +568,7 @@ async function main(deps = defaultDeps) {
440
568
  deps.log(`Drafted release notes: ${result.path} (previous tag: ${result.previousTag ?? "none"}, ${result.entryCount} entries)`);
441
569
  }
442
570
  if (import.meta.url === `file://${process.argv[1]}`) {
443
- main().catch((error) => {
444
- console.error(error);
445
- process.exit(1);
446
- });
571
+ void runCiEntrypoint("releaseNotes", main);
447
572
  }
448
573
  export {
449
574
  main,
@@ -127,6 +127,157 @@ function defaultModelFor(tier) {
127
127
  return current.id;
128
128
  }
129
129
 
130
+ // ../../modules/core/dist/redactSecrets.js
131
+ var REDACTED = "[REDACTED]";
132
+ var KNOWN_SECRET_PATTERNS = [
133
+ /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
134
+ /gh[pousr]_[A-Za-z0-9]{36,}/g,
135
+ /github_pat_[A-Za-z0-9_]{20,}/g,
136
+ /sk-ant-[A-Za-z0-9_-]{20,}/g,
137
+ /sk-[A-Za-z0-9]{20,}/g,
138
+ /AKIA[0-9A-Z]{16}/g,
139
+ /xox[baprs]-[A-Za-z0-9-]{10,}/g,
140
+ /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g
141
+ ];
142
+ var SECRET_KEYWORD = "(?:API[_-]?KEY|SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE[_-]?KEY|CREDENTIAL)";
143
+ var ASSIGNMENT_PATTERN = new RegExp(`((?:[A-Za-z0-9]+[_-])*${SECRET_KEYWORD}(?:[_-][A-Za-z0-9]+)*\\s*[:=]\\s*)(["']?)([^"'\`\\s]+)\\2`, "gi");
144
+ function redactText(text) {
145
+ let result = text;
146
+ for (const pattern of KNOWN_SECRET_PATTERNS) {
147
+ result = result.replace(pattern, REDACTED);
148
+ }
149
+ result = result.replace(ASSIGNMENT_PATTERN, (_match, prefix, quote) => `${prefix}${quote}${REDACTED}${quote}`);
150
+ return result;
151
+ }
152
+
153
+ // ../../modules/core/dist/ciHealth.js
154
+ var EMPTY_ACTION_HEALTH = {
155
+ lastSuccessAt: null,
156
+ lastFailureAt: null,
157
+ lastFailureMessage: null,
158
+ lastFailureRunUrl: null,
159
+ consecutiveFailures: 0
160
+ };
161
+ var MAX_MESSAGE_CHARS = 2e3;
162
+ function healthPath(projectName) {
163
+ return `projects/${projectName}/health.json`;
164
+ }
165
+ function describeError(error) {
166
+ const raw = error instanceof Error ? `${error.name}: ${error.message}` : typeof error === "string" ? error : JSON.stringify(error);
167
+ const text = raw ?? "Unknown error";
168
+ return redactText(text.length > MAX_MESSAGE_CHARS ? `${text.slice(0, MAX_MESSAGE_CHARS)}\u2026` : text);
169
+ }
170
+ function applySuccess(current, source, now) {
171
+ const previous = current[source] ?? EMPTY_ACTION_HEALTH;
172
+ return {
173
+ ...current,
174
+ // The failure details are deliberately kept, not cleared: "it failed
175
+ // on Tuesday and has worked since" is worth being able to read.
176
+ [source]: { ...previous, lastSuccessAt: now.toISOString(), consecutiveFailures: 0 }
177
+ };
178
+ }
179
+ function applyFailure(current, source, error, runUrl, now) {
180
+ const previous = current[source] ?? EMPTY_ACTION_HEALTH;
181
+ return {
182
+ ...current,
183
+ [source]: {
184
+ ...previous,
185
+ lastFailureAt: now.toISOString(),
186
+ lastFailureMessage: describeError(error),
187
+ lastFailureRunUrl: runUrl,
188
+ consecutiveFailures: previous.consecutiveFailures + 1
189
+ }
190
+ };
191
+ }
192
+ async function getHealthFile(octokit, { owner, repo, projectName }) {
193
+ try {
194
+ const { data } = await octokit.rest.repos.getContent({ owner, repo, path: healthPath(projectName) });
195
+ if (Array.isArray(data) || data.type !== "file") {
196
+ return { health: {} };
197
+ }
198
+ return { health: JSON.parse(Buffer.from(data.content, "base64").toString("utf-8")), sha: data.sha };
199
+ } catch {
200
+ return { health: {} };
201
+ }
202
+ }
203
+ async function writeHealth(octokit, params, update, message) {
204
+ const { owner, repo, projectName, branch } = params;
205
+ await retryOnConflict(async () => {
206
+ const { health, sha } = await getHealthFile(octokit, params);
207
+ await octokit.rest.repos.createOrUpdateFileContents({
208
+ owner,
209
+ repo,
210
+ path: healthPath(projectName),
211
+ message,
212
+ content: Buffer.from(JSON.stringify(update(health), null, 2), "utf-8").toString("base64"),
213
+ branch,
214
+ sha
215
+ });
216
+ });
217
+ }
218
+ async function recordSuccess(octokit, params, source, now = /* @__PURE__ */ new Date()) {
219
+ await writeHealth(octokit, params, (h) => applySuccess(h, source, now), `Record ${source} success: ${params.projectName}`);
220
+ }
221
+ async function recordFailure(octokit, params, source, error, runUrl, now = /* @__PURE__ */ new Date()) {
222
+ await writeHealth(octokit, params, (h) => applyFailure(h, source, error, runUrl, now), `Record ${source} failure: ${params.projectName}`);
223
+ }
224
+ function currentRunUrl(env) {
225
+ const { GITHUB_SERVER_URL, GITHUB_REPOSITORY, GITHUB_RUN_ID } = env;
226
+ if (!GITHUB_SERVER_URL || !GITHUB_REPOSITORY || !GITHUB_RUN_ID) {
227
+ return null;
228
+ }
229
+ return `${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}`;
230
+ }
231
+
232
+ // ../../modules/core/dist/ciEntrypoint.js
233
+ var VAULT_BRANCH = "main";
234
+ function healthParams(env) {
235
+ const projectName = env.PROJECT_NAME;
236
+ const vaultRepo = env.VAULT_REPO;
237
+ const token = env.DEVKEEP_GITHUB_TOKEN;
238
+ if (!projectName || !vaultRepo || !token) {
239
+ return null;
240
+ }
241
+ const [owner, repo] = vaultRepo.split("/");
242
+ if (!owner || !repo) {
243
+ return null;
244
+ }
245
+ return { owner, repo, projectName, branch: VAULT_BRANCH };
246
+ }
247
+ var defaultDeps = {
248
+ env: process.env,
249
+ createGithubClient,
250
+ recordSuccess,
251
+ recordFailure,
252
+ logError: console.error,
253
+ exit: (code) => process.exit(code)
254
+ };
255
+ async function runCiEntrypoint(source, main2, deps = defaultDeps) {
256
+ const params = healthParams(deps.env);
257
+ const octokit = params ? deps.createGithubClient(deps.env.DEVKEEP_GITHUB_TOKEN) : null;
258
+ try {
259
+ await main2();
260
+ } catch (error) {
261
+ deps.logError(error);
262
+ if (params && octokit) {
263
+ try {
264
+ await deps.recordFailure(octokit, params, source, error, currentRunUrl(deps.env));
265
+ } catch (recordingError) {
266
+ deps.logError(`Could not record the failure to the vault: ${String(recordingError)}`);
267
+ }
268
+ }
269
+ deps.exit(1);
270
+ return;
271
+ }
272
+ if (params && octokit) {
273
+ try {
274
+ await deps.recordSuccess(octokit, params, source);
275
+ } catch (recordingError) {
276
+ deps.logError(`Ran successfully, but could not record it to the vault: ${String(recordingError)}`);
277
+ }
278
+ }
279
+ }
280
+
130
281
  // ../../modules/devlore/dist/decisionEntries.js
131
282
  function decisionEntriesDir(projectName) {
132
283
  return `projects/${projectName}/decisions`;
@@ -186,29 +337,6 @@ ${input.decisions.join("\n\n")}` : "",
186
337
  ].filter((line) => line !== "").join("\n");
187
338
  }
188
339
 
189
- // ../../modules/devlore/dist/redact.js
190
- var REDACTED = "[REDACTED]";
191
- var KNOWN_SECRET_PATTERNS = [
192
- /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
193
- /gh[pousr]_[A-Za-z0-9]{36,}/g,
194
- /github_pat_[A-Za-z0-9_]{20,}/g,
195
- /sk-ant-[A-Za-z0-9_-]{20,}/g,
196
- /sk-[A-Za-z0-9]{20,}/g,
197
- /AKIA[0-9A-Z]{16}/g,
198
- /xox[baprs]-[A-Za-z0-9-]{10,}/g,
199
- /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g
200
- ];
201
- var SECRET_KEYWORD = "(?:API[_-]?KEY|SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE[_-]?KEY|CREDENTIAL)";
202
- var ASSIGNMENT_PATTERN = new RegExp(`((?:[A-Za-z0-9]+[_-])*${SECRET_KEYWORD}(?:[_-][A-Za-z0-9]+)*\\s*[:=]\\s*)(["']?)([^"'\`\\s]+)\\2`, "gi");
203
- function redactText(text) {
204
- let result = text;
205
- for (const pattern of KNOWN_SECRET_PATTERNS) {
206
- result = result.replace(pattern, REDACTED);
207
- }
208
- result = result.replace(ASSIGNMENT_PATTERN, (_match, prefix, quote) => `${prefix}${quote}${REDACTED}${quote}`);
209
- return result;
210
- }
211
-
212
340
  // ../../modules/devlore/dist/ciUsage.js
213
341
  var EMPTY_PROJECT_USAGE = {
214
342
  draftLogEntry: ZERO_USAGE,
@@ -358,7 +486,7 @@ function isNotFound4(error) {
358
486
  var MODEL_TIER = "balanced";
359
487
  var MAX_TOKENS = 2e4;
360
488
  var MAX_CONTINUATIONS = 8;
361
- var VAULT_BRANCH = "main";
489
+ var VAULT_BRANCH2 = "main";
362
490
  async function readVaultFile(octokit, owner, repo, path) {
363
491
  const { data } = await octokit.rest.repos.getContent({ owner, repo, path }).catch(() => ({ data: null }));
364
492
  return data && !Array.isArray(data) && data.type === "file" ? Buffer.from(data.content, "base64").toString("utf-8") : "";
@@ -403,12 +531,12 @@ ${text.trim()}
403
531
  path: vaultPath,
404
532
  message: `Mirror onboarding guide: ${projectName}`,
405
533
  content: Buffer.from(mirrored, "utf-8").toString("base64"),
406
- branch: VAULT_BRANCH,
534
+ branch: VAULT_BRANCH2,
407
535
  sha: existingVault.sha
408
536
  });
409
537
  }
410
538
  if (usage.inputTokens > 0 || usage.outputTokens > 0) {
411
- await recordUsage(vaultOctokit, { owner: vaultOwner, repo: vaultRepoName, projectName, branch: VAULT_BRANCH }, "syncOnboarding", usage);
539
+ await recordUsage(vaultOctokit, { owner: vaultOwner, repo: vaultRepoName, projectName, branch: VAULT_BRANCH2 }, "syncOnboarding", usage);
412
540
  }
413
541
  return { path: ONBOARDING_PATH, vaultPath, content, usage };
414
542
  }
@@ -421,14 +549,14 @@ function requireEnv(env, name) {
421
549
  }
422
550
  return value;
423
551
  }
424
- var defaultDeps = {
552
+ var defaultDeps2 = {
425
553
  env: process.env,
426
554
  syncOnboarding,
427
555
  createGithubClient,
428
556
  createClaudeClient,
429
557
  log: console.log
430
558
  };
431
- async function main(deps = defaultDeps) {
559
+ async function main(deps = defaultDeps2) {
432
560
  const { env } = deps;
433
561
  const projectName = requireEnv(env, "PROJECT_NAME");
434
562
  const [projectOwner, projectRepoName] = requireEnv(env, "PROJECT_REPO").split("/");
@@ -448,10 +576,7 @@ async function main(deps = defaultDeps) {
448
576
  deps.log(`Synced onboarding guide: ${result.path} (mirrored to ${result.vaultPath})`);
449
577
  }
450
578
  if (import.meta.url === `file://${process.argv[1]}`) {
451
- main().catch((error) => {
452
- console.error(error);
453
- process.exit(1);
454
- });
579
+ void runCiEntrypoint("syncOnboarding", main);
455
580
  }
456
581
  export {
457
582
  main,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@starterculture/devkeep-actions",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Devkeep's GitHub Actions entrypoints, bundled for CI runners.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",