@starterculture/devkeep-actions 0.4.1 → 0.5.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.
@@ -127,6 +127,159 @@ 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 missing = ["PROJECT_NAME", "VAULT_REPO", "DEVKEEP_GITHUB_TOKEN"].filter((name) => !env[name]);
236
+ if (missing.length > 0) {
237
+ return { missing: [...missing] };
238
+ }
239
+ const [owner, repo] = env.VAULT_REPO.split("/");
240
+ if (!owner || !repo) {
241
+ return { missing: ["VAULT_REPO (expected owner/repo)"] };
242
+ }
243
+ return { params: { owner, repo, projectName: env.PROJECT_NAME, branch: VAULT_BRANCH } };
244
+ }
245
+ var defaultDeps = {
246
+ env: process.env,
247
+ createGithubClient,
248
+ recordSuccess,
249
+ recordFailure,
250
+ logError: console.error,
251
+ exit: (code) => process.exit(code)
252
+ };
253
+ async function runCiEntrypoint(source, main2, deps = defaultDeps) {
254
+ const resolved = healthParams(deps.env);
255
+ const params = "params" in resolved ? resolved.params : null;
256
+ if (!params) {
257
+ deps.logError(`Health not recorded for "${source}": ${resolved.missing.join(", ")} not set.`);
258
+ }
259
+ const octokit = params ? deps.createGithubClient(deps.env.DEVKEEP_GITHUB_TOKEN) : null;
260
+ try {
261
+ await main2();
262
+ } catch (error) {
263
+ deps.logError(error);
264
+ if (params && octokit) {
265
+ try {
266
+ await deps.recordFailure(octokit, params, source, error, currentRunUrl(deps.env));
267
+ } catch (recordingError) {
268
+ deps.logError(`Could not record the failure to the vault: ${String(recordingError)}`);
269
+ }
270
+ }
271
+ deps.exit(1);
272
+ return;
273
+ }
274
+ if (params && octokit) {
275
+ try {
276
+ await deps.recordSuccess(octokit, params, source);
277
+ } catch (recordingError) {
278
+ deps.logError(`Ran successfully, but could not record it to the vault: ${String(recordingError)}`);
279
+ }
280
+ }
281
+ }
282
+
130
283
  // ../../modules/devlore/dist/ciUsage.js
131
284
  var EMPTY_PROJECT_USAGE = {
132
285
  draftLogEntry: ZERO_USAGE,
@@ -429,14 +582,14 @@ function requireEnv(env, name) {
429
582
  }
430
583
  return value;
431
584
  }
432
- var defaultDeps = {
585
+ var defaultDeps2 = {
433
586
  env: process.env,
434
587
  consolidateRelease,
435
588
  createGithubClient,
436
589
  createClaudeClient,
437
590
  log: console.log
438
591
  };
439
- async function main(deps = defaultDeps) {
592
+ async function main(deps = defaultDeps2) {
440
593
  const { env } = deps;
441
594
  const projectName = requireEnv(env, "PROJECT_NAME");
442
595
  const [projectOwner, projectRepoName] = requireEnv(env, "PROJECT_REPO").split("/");
@@ -459,10 +612,7 @@ async function main(deps = defaultDeps) {
459
612
  deps.log(`Opened consolidation PR #${result.prNumber}: ${result.prUrl}`);
460
613
  }
461
614
  if (import.meta.url === `file://${process.argv[1]}`) {
462
- main().catch((error) => {
463
- console.error(error);
464
- process.exit(1);
465
- });
615
+ void runCiEntrypoint("consolidateRelease", main);
466
616
  }
467
617
  export {
468
618
  main,
@@ -24,6 +24,159 @@ 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 missing = ["PROJECT_NAME", "VAULT_REPO", "DEVKEEP_GITHUB_TOKEN"].filter((name) => !env[name]);
133
+ if (missing.length > 0) {
134
+ return { missing: [...missing] };
135
+ }
136
+ const [owner, repo] = env.VAULT_REPO.split("/");
137
+ if (!owner || !repo) {
138
+ return { missing: ["VAULT_REPO (expected owner/repo)"] };
139
+ }
140
+ return { params: { owner, repo, projectName: env.PROJECT_NAME, branch: VAULT_BRANCH } };
141
+ }
142
+ var defaultDeps = {
143
+ env: process.env,
144
+ createGithubClient,
145
+ recordSuccess,
146
+ recordFailure,
147
+ logError: console.error,
148
+ exit: (code) => process.exit(code)
149
+ };
150
+ async function runCiEntrypoint(source, main2, deps = defaultDeps) {
151
+ const resolved = healthParams(deps.env);
152
+ const params = "params" in resolved ? resolved.params : null;
153
+ if (!params) {
154
+ deps.logError(`Health not recorded for "${source}": ${resolved.missing.join(", ")} not set.`);
155
+ }
156
+ const octokit = params ? deps.createGithubClient(deps.env.DEVKEEP_GITHUB_TOKEN) : null;
157
+ try {
158
+ await main2();
159
+ } catch (error) {
160
+ deps.logError(error);
161
+ if (params && octokit) {
162
+ try {
163
+ await deps.recordFailure(octokit, params, source, error, currentRunUrl(deps.env));
164
+ } catch (recordingError) {
165
+ deps.logError(`Could not record the failure to the vault: ${String(recordingError)}`);
166
+ }
167
+ }
168
+ deps.exit(1);
169
+ return;
170
+ }
171
+ if (params && octokit) {
172
+ try {
173
+ await deps.recordSuccess(octokit, params, source);
174
+ } catch (recordingError) {
175
+ deps.logError(`Ran successfully, but could not record it to the vault: ${String(recordingError)}`);
176
+ }
177
+ }
178
+ }
179
+
27
180
  // ../../modules/devlore/dist/diff.js
28
181
  function toDiffFile(file) {
29
182
  return {
@@ -70,8 +223,8 @@ var SECRET_PREFIX_PATTERNS = [
70
223
  // JWT-shaped
71
224
  ];
72
225
  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");
226
+ var SECRET_KEYWORD2 = "(?:API[_-]?KEY|SECRET|TOKEN|PASSWORD|PASSWD|PRIVATE[_-]?KEY|CREDENTIAL)";
227
+ var SECRET_ASSIGNMENT_PATTERN = new RegExp(`(?:[A-Za-z0-9]+[_-])*${SECRET_KEYWORD2}(?:[_-][A-Za-z0-9]+)*\\s*[:=]\\s*["']([^"'\`]+)["']`, "i");
75
228
 
76
229
  // ../../modules/devcrypt/dist/candidateScan.js
77
230
  function addedLines(patch) {
@@ -332,13 +485,13 @@ function requireEnv(env, name) {
332
485
  }
333
486
  return value;
334
487
  }
335
- var defaultDeps = {
488
+ var defaultDeps2 = {
336
489
  env: process.env,
337
490
  scanCryptingCandidates,
338
491
  createGithubClient,
339
492
  log: console.log
340
493
  };
341
- async function main(deps = defaultDeps) {
494
+ async function main(deps = defaultDeps2) {
342
495
  const { env } = deps;
343
496
  const [projectOwner, projectRepoName] = requireEnv(env, "PROJECT_REPO").split("/");
344
497
  const [vaultOwner, vaultRepo] = requireEnv(env, "VAULT_REPO").split("/");
@@ -360,10 +513,7 @@ async function main(deps = defaultDeps) {
360
513
  deps.log(`Flagged ${result.candidatesFlagged} crypting candidate(s)`);
361
514
  }
362
515
  if (import.meta.url === `file://${process.argv[1]}`) {
363
- main().catch((error) => {
364
- console.error(error);
365
- process.exit(1);
366
- });
516
+ void runCiEntrypoint("cryptingCandidatesScan", main);
367
517
  }
368
518
  export {
369
519
  main,
@@ -127,6 +127,159 @@ 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 missing = ["PROJECT_NAME", "VAULT_REPO", "DEVKEEP_GITHUB_TOKEN"].filter((name) => !env[name]);
236
+ if (missing.length > 0) {
237
+ return { missing: [...missing] };
238
+ }
239
+ const [owner, repo] = env.VAULT_REPO.split("/");
240
+ if (!owner || !repo) {
241
+ return { missing: ["VAULT_REPO (expected owner/repo)"] };
242
+ }
243
+ return { params: { owner, repo, projectName: env.PROJECT_NAME, branch: VAULT_BRANCH } };
244
+ }
245
+ var defaultDeps = {
246
+ env: process.env,
247
+ createGithubClient,
248
+ recordSuccess,
249
+ recordFailure,
250
+ logError: console.error,
251
+ exit: (code) => process.exit(code)
252
+ };
253
+ async function runCiEntrypoint(source, main2, deps = defaultDeps) {
254
+ const resolved = healthParams(deps.env);
255
+ const params = "params" in resolved ? resolved.params : null;
256
+ if (!params) {
257
+ deps.logError(`Health not recorded for "${source}": ${resolved.missing.join(", ")} not set.`);
258
+ }
259
+ const octokit = params ? deps.createGithubClient(deps.env.DEVKEEP_GITHUB_TOKEN) : null;
260
+ try {
261
+ await main2();
262
+ } catch (error) {
263
+ deps.logError(error);
264
+ if (params && octokit) {
265
+ try {
266
+ await deps.recordFailure(octokit, params, source, error, currentRunUrl(deps.env));
267
+ } catch (recordingError) {
268
+ deps.logError(`Could not record the failure to the vault: ${String(recordingError)}`);
269
+ }
270
+ }
271
+ deps.exit(1);
272
+ return;
273
+ }
274
+ if (params && octokit) {
275
+ try {
276
+ await deps.recordSuccess(octokit, params, source);
277
+ } catch (recordingError) {
278
+ deps.logError(`Ran successfully, but could not record it to the vault: ${String(recordingError)}`);
279
+ }
280
+ }
281
+ }
282
+
130
283
  // ../../modules/devlore/dist/ciUsage.js
131
284
  var EMPTY_PROJECT_USAGE = {
132
285
  draftLogEntry: ZERO_USAGE,
@@ -484,19 +637,6 @@ function routeDiffByModule(files, manifest) {
484
637
  }
485
638
 
486
639
  // ../../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
640
  var SENSITIVE_FILENAME_PATTERNS = [
501
641
  /(^|\/)\.env(\..+)?$/,
502
642
  /\.pem$/,
@@ -508,14 +648,6 @@ var SENSITIVE_FILENAME_PATTERNS = [
508
648
  /credentials\.json$/,
509
649
  /secrets?\.(json|ya?ml|toml)$/
510
650
  ];
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
651
  function isSensitiveFilename(filename) {
520
652
  return SENSITIVE_FILENAME_PATTERNS.some((pattern) => pattern.test(filename));
521
653
  }
@@ -533,7 +665,7 @@ function redactProjectDiff(diff) {
533
665
  }
534
666
 
535
667
  // ../../modules/devlore/dist/actions/draftLogEntry.js
536
- var VAULT_BRANCH = "main";
668
+ var VAULT_BRANCH2 = "main";
537
669
  var MODEL_TIER = "balanced";
538
670
  var MAX_TOKENS = 2048;
539
671
  function logEntryPath(projectName, commitSha, moduleId) {
@@ -567,7 +699,7 @@ async function syncProductDoc(projectOctokit, vaultOctokit, { projectOwner, proj
567
699
  path: productDocPath(projectName, moduleId),
568
700
  message: `Sync PRODUCT.md: ${projectName}@${headSha.slice(0, 7)}`,
569
701
  content: Buffer.from(redactedDoc, "utf-8").toString("base64"),
570
- branch: VAULT_BRANCH,
702
+ branch: VAULT_BRANCH2,
571
703
  sha: existing.sha
572
704
  });
573
705
  });
@@ -600,7 +732,7 @@ ${entryBody}
600
732
  path: entryPath,
601
733
  message: `Draft log entry: ${projectName}@${shortSha}${moduleName ? ` (${moduleName})` : ""}`,
602
734
  content: Buffer.from(entryContent, "utf-8").toString("base64"),
603
- branch: VAULT_BRANCH
735
+ branch: VAULT_BRANCH2
604
736
  }));
605
737
  let decisionPath = null;
606
738
  if (decisionEntry) {
@@ -615,7 +747,7 @@ ${decisionEntry}
615
747
  path: decisionPath,
616
748
  message: `Draft decision entry: ${projectName}@${shortSha}${moduleName ? ` (${moduleName})` : ""}`,
617
749
  content: Buffer.from(decisionContent, "utf-8").toString("base64"),
618
- branch: VAULT_BRANCH
750
+ branch: VAULT_BRANCH2
619
751
  }));
620
752
  }
621
753
  return { moduleId, logEntryPath: entryPath, decisionEntryPath: decisionPath, usage };
@@ -668,7 +800,7 @@ async function draftLogEntry({ projectOctokit, vaultOctokit, claude, projectOwne
668
800
  });
669
801
  }
670
802
  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);
803
+ await recordUsage(vaultOctokit, { owner: vaultOwner, repo: vaultRepoName, projectName, branch: VAULT_BRANCH2 }, "draftLogEntry", totalUsage);
672
804
  return results;
673
805
  }
674
806
 
@@ -680,14 +812,14 @@ function requireEnv(env, name) {
680
812
  }
681
813
  return value;
682
814
  }
683
- var defaultDeps = {
815
+ var defaultDeps2 = {
684
816
  env: process.env,
685
817
  draftLogEntry,
686
818
  createGithubClient,
687
819
  createClaudeClient,
688
820
  log: console.log
689
821
  };
690
- async function main(deps = defaultDeps) {
822
+ async function main(deps = defaultDeps2) {
691
823
  const { env } = deps;
692
824
  const projectName = requireEnv(env, "PROJECT_NAME");
693
825
  const [projectOwner, projectRepoName] = requireEnv(env, "PROJECT_REPO").split("/");
@@ -717,10 +849,7 @@ async function main(deps = defaultDeps) {
717
849
  }
718
850
  }
719
851
  if (import.meta.url === `file://${process.argv[1]}`) {
720
- main().catch((error) => {
721
- console.error(error);
722
- process.exit(1);
723
- });
852
+ void runCiEntrypoint("draftLogEntry", main);
724
853
  }
725
854
  export {
726
855
  main,
@@ -127,6 +127,159 @@ 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 missing = ["PROJECT_NAME", "VAULT_REPO", "DEVKEEP_GITHUB_TOKEN"].filter((name) => !env[name]);
236
+ if (missing.length > 0) {
237
+ return { missing: [...missing] };
238
+ }
239
+ const [owner, repo] = env.VAULT_REPO.split("/");
240
+ if (!owner || !repo) {
241
+ return { missing: ["VAULT_REPO (expected owner/repo)"] };
242
+ }
243
+ return { params: { owner, repo, projectName: env.PROJECT_NAME, branch: VAULT_BRANCH } };
244
+ }
245
+ var defaultDeps = {
246
+ env: process.env,
247
+ createGithubClient,
248
+ recordSuccess,
249
+ recordFailure,
250
+ logError: console.error,
251
+ exit: (code) => process.exit(code)
252
+ };
253
+ async function runCiEntrypoint(source, main2, deps = defaultDeps) {
254
+ const resolved = healthParams(deps.env);
255
+ const params = "params" in resolved ? resolved.params : null;
256
+ if (!params) {
257
+ deps.logError(`Health not recorded for "${source}": ${resolved.missing.join(", ")} not set.`);
258
+ }
259
+ const octokit = params ? deps.createGithubClient(deps.env.DEVKEEP_GITHUB_TOKEN) : null;
260
+ try {
261
+ await main2();
262
+ } catch (error) {
263
+ deps.logError(error);
264
+ if (params && octokit) {
265
+ try {
266
+ await deps.recordFailure(octokit, params, source, error, currentRunUrl(deps.env));
267
+ } catch (recordingError) {
268
+ deps.logError(`Could not record the failure to the vault: ${String(recordingError)}`);
269
+ }
270
+ }
271
+ deps.exit(1);
272
+ return;
273
+ }
274
+ if (params && octokit) {
275
+ try {
276
+ await deps.recordSuccess(octokit, params, source);
277
+ } catch (recordingError) {
278
+ deps.logError(`Ran successfully, but could not record it to the vault: ${String(recordingError)}`);
279
+ }
280
+ }
281
+ }
282
+
130
283
  // ../../modules/devlore/dist/prompt.js
131
284
  function buildReleaseNotesPrompt(input) {
132
285
  return [
@@ -150,29 +303,6 @@ function buildReleaseNotesPrompt(input) {
150
303
  ].join("\n");
151
304
  }
152
305
 
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
306
  // ../../modules/devlore/dist/ciUsage.js
177
307
  var EMPTY_PROJECT_USAGE = {
178
308
  draftLogEntry: ZERO_USAGE,
@@ -354,7 +484,7 @@ function releaseNotesDir(projectName) {
354
484
  var MODEL_TIER = "balanced";
355
485
  var MAX_TOKENS = 2e4;
356
486
  var MAX_CONTINUATIONS = 8;
357
- var VAULT_BRANCH = "main";
487
+ var VAULT_BRANCH2 = "main";
358
488
  async function draftReleaseNotes({ projectOctokit, vaultOctokit, claude, projectOwner, projectRepoName, vaultOwner, vaultRepoName, projectName, tag }) {
359
489
  const { logEntries, previousTag } = await selectLogEntriesSincePreviousRelease({
360
490
  vaultOctokit,
@@ -386,11 +516,11 @@ ${text.trim()}
386
516
  path,
387
517
  message: `Draft release notes: ${projectName} ${tag}`,
388
518
  content: Buffer.from(content, "utf-8").toString("base64"),
389
- branch: VAULT_BRANCH,
519
+ branch: VAULT_BRANCH2,
390
520
  sha: await getExistingSha(vaultOctokit, vaultOwner, vaultRepoName, path)
391
521
  });
392
522
  if (usage.inputTokens > 0 || usage.outputTokens > 0) {
393
- await recordUsage(vaultOctokit, { owner: vaultOwner, repo: vaultRepoName, projectName, branch: VAULT_BRANCH }, "releaseNotes", usage);
523
+ await recordUsage(vaultOctokit, { owner: vaultOwner, repo: vaultRepoName, projectName, branch: VAULT_BRANCH2 }, "releaseNotes", usage);
394
524
  }
395
525
  return { path, previousTag, entryCount: logEntries.length, content, usage };
396
526
  }
@@ -411,14 +541,14 @@ function requireEnv(env, name) {
411
541
  }
412
542
  return value;
413
543
  }
414
- var defaultDeps = {
544
+ var defaultDeps2 = {
415
545
  env: process.env,
416
546
  draftReleaseNotes,
417
547
  createGithubClient,
418
548
  createClaudeClient,
419
549
  log: console.log
420
550
  };
421
- async function main(deps = defaultDeps) {
551
+ async function main(deps = defaultDeps2) {
422
552
  const { env } = deps;
423
553
  const projectName = requireEnv(env, "PROJECT_NAME");
424
554
  const [projectOwner, projectRepoName] = requireEnv(env, "PROJECT_REPO").split("/");
@@ -440,10 +570,7 @@ async function main(deps = defaultDeps) {
440
570
  deps.log(`Drafted release notes: ${result.path} (previous tag: ${result.previousTag ?? "none"}, ${result.entryCount} entries)`);
441
571
  }
442
572
  if (import.meta.url === `file://${process.argv[1]}`) {
443
- main().catch((error) => {
444
- console.error(error);
445
- process.exit(1);
446
- });
573
+ void runCiEntrypoint("releaseNotes", main);
447
574
  }
448
575
  export {
449
576
  main,
@@ -127,6 +127,159 @@ 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 missing = ["PROJECT_NAME", "VAULT_REPO", "DEVKEEP_GITHUB_TOKEN"].filter((name) => !env[name]);
236
+ if (missing.length > 0) {
237
+ return { missing: [...missing] };
238
+ }
239
+ const [owner, repo] = env.VAULT_REPO.split("/");
240
+ if (!owner || !repo) {
241
+ return { missing: ["VAULT_REPO (expected owner/repo)"] };
242
+ }
243
+ return { params: { owner, repo, projectName: env.PROJECT_NAME, branch: VAULT_BRANCH } };
244
+ }
245
+ var defaultDeps = {
246
+ env: process.env,
247
+ createGithubClient,
248
+ recordSuccess,
249
+ recordFailure,
250
+ logError: console.error,
251
+ exit: (code) => process.exit(code)
252
+ };
253
+ async function runCiEntrypoint(source, main2, deps = defaultDeps) {
254
+ const resolved = healthParams(deps.env);
255
+ const params = "params" in resolved ? resolved.params : null;
256
+ if (!params) {
257
+ deps.logError(`Health not recorded for "${source}": ${resolved.missing.join(", ")} not set.`);
258
+ }
259
+ const octokit = params ? deps.createGithubClient(deps.env.DEVKEEP_GITHUB_TOKEN) : null;
260
+ try {
261
+ await main2();
262
+ } catch (error) {
263
+ deps.logError(error);
264
+ if (params && octokit) {
265
+ try {
266
+ await deps.recordFailure(octokit, params, source, error, currentRunUrl(deps.env));
267
+ } catch (recordingError) {
268
+ deps.logError(`Could not record the failure to the vault: ${String(recordingError)}`);
269
+ }
270
+ }
271
+ deps.exit(1);
272
+ return;
273
+ }
274
+ if (params && octokit) {
275
+ try {
276
+ await deps.recordSuccess(octokit, params, source);
277
+ } catch (recordingError) {
278
+ deps.logError(`Ran successfully, but could not record it to the vault: ${String(recordingError)}`);
279
+ }
280
+ }
281
+ }
282
+
130
283
  // ../../modules/devlore/dist/decisionEntries.js
131
284
  function decisionEntriesDir(projectName) {
132
285
  return `projects/${projectName}/decisions`;
@@ -186,29 +339,6 @@ ${input.decisions.join("\n\n")}` : "",
186
339
  ].filter((line) => line !== "").join("\n");
187
340
  }
188
341
 
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
342
  // ../../modules/devlore/dist/ciUsage.js
213
343
  var EMPTY_PROJECT_USAGE = {
214
344
  draftLogEntry: ZERO_USAGE,
@@ -358,7 +488,7 @@ function isNotFound4(error) {
358
488
  var MODEL_TIER = "balanced";
359
489
  var MAX_TOKENS = 2e4;
360
490
  var MAX_CONTINUATIONS = 8;
361
- var VAULT_BRANCH = "main";
491
+ var VAULT_BRANCH2 = "main";
362
492
  async function readVaultFile(octokit, owner, repo, path) {
363
493
  const { data } = await octokit.rest.repos.getContent({ owner, repo, path }).catch(() => ({ data: null }));
364
494
  return data && !Array.isArray(data) && data.type === "file" ? Buffer.from(data.content, "base64").toString("utf-8") : "";
@@ -403,12 +533,12 @@ ${text.trim()}
403
533
  path: vaultPath,
404
534
  message: `Mirror onboarding guide: ${projectName}`,
405
535
  content: Buffer.from(mirrored, "utf-8").toString("base64"),
406
- branch: VAULT_BRANCH,
536
+ branch: VAULT_BRANCH2,
407
537
  sha: existingVault.sha
408
538
  });
409
539
  }
410
540
  if (usage.inputTokens > 0 || usage.outputTokens > 0) {
411
- await recordUsage(vaultOctokit, { owner: vaultOwner, repo: vaultRepoName, projectName, branch: VAULT_BRANCH }, "syncOnboarding", usage);
541
+ await recordUsage(vaultOctokit, { owner: vaultOwner, repo: vaultRepoName, projectName, branch: VAULT_BRANCH2 }, "syncOnboarding", usage);
412
542
  }
413
543
  return { path: ONBOARDING_PATH, vaultPath, content, usage };
414
544
  }
@@ -421,14 +551,14 @@ function requireEnv(env, name) {
421
551
  }
422
552
  return value;
423
553
  }
424
- var defaultDeps = {
554
+ var defaultDeps2 = {
425
555
  env: process.env,
426
556
  syncOnboarding,
427
557
  createGithubClient,
428
558
  createClaudeClient,
429
559
  log: console.log
430
560
  };
431
- async function main(deps = defaultDeps) {
561
+ async function main(deps = defaultDeps2) {
432
562
  const { env } = deps;
433
563
  const projectName = requireEnv(env, "PROJECT_NAME");
434
564
  const [projectOwner, projectRepoName] = requireEnv(env, "PROJECT_REPO").split("/");
@@ -448,10 +578,7 @@ async function main(deps = defaultDeps) {
448
578
  deps.log(`Synced onboarding guide: ${result.path} (mirrored to ${result.vaultPath})`);
449
579
  }
450
580
  if (import.meta.url === `file://${process.argv[1]}`) {
451
- main().catch((error) => {
452
- console.error(error);
453
- process.exit(1);
454
- });
581
+ void runCiEntrypoint("syncOnboarding", main);
455
582
  }
456
583
  export {
457
584
  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.1",
4
4
  "description": "Devkeep's GitHub Actions entrypoints, bundled for CI runners.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",