@starterculture/devkeep-actions 0.4.0 → 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,
@@ -190,22 +341,22 @@ function isNotFound(error) {
190
341
  return typeof error === "object" && error !== null && "status" in error && error.status === 404;
191
342
  }
192
343
 
193
- // ../../modules/devlore/dist/masterDocs.js
194
- function masterDocsPath(projectName) {
195
- return `projects/${projectName}/master/README.md`;
344
+ // ../../modules/devlore/dist/canonicalDocs.js
345
+ function canonicalDocsPath(projectName) {
346
+ return `projects/${projectName}/canonical/README.md`;
196
347
  }
197
- async function getMasterDocs(octokit, params) {
198
- return (await getMasterDocsFile(octokit, params)).content;
348
+ async function getCanonicalDocs(octokit, params) {
349
+ return (await getCanonicalDocsFile(octokit, params)).content;
199
350
  }
200
- async function getMasterDocsSha(octokit, params) {
201
- return (await getMasterDocsFile(octokit, params)).sha;
351
+ async function getCanonicalDocsSha(octokit, params) {
352
+ return (await getCanonicalDocsFile(octokit, params)).sha;
202
353
  }
203
- async function getMasterDocsFile(octokit, { owner, repo, projectName }) {
354
+ async function getCanonicalDocsFile(octokit, { owner, repo, projectName }) {
204
355
  try {
205
356
  const { data } = await octokit.rest.repos.getContent({
206
357
  owner,
207
358
  repo,
208
- path: masterDocsPath(projectName)
359
+ path: canonicalDocsPath(projectName)
209
360
  });
210
361
  if (Array.isArray(data) || data.type !== "file") {
211
362
  return { content: "" };
@@ -232,13 +383,13 @@ ${entry.content}`).join("\n\n");
232
383
  `Previous release: ${input.previousTag ?? "(none \u2014 this is the first release)"}`,
233
384
  `Date: ${input.date}`,
234
385
  "",
235
- "Current master docs (may be empty if none exist yet):",
236
- input.masterDocs || "(none yet)",
386
+ "Current canonical docs (may be empty if none exist yet):",
387
+ input.canonicalDocs || "(none yet)",
237
388
  "",
238
389
  "Log entries since the previous release:",
239
390
  entriesText || "(none)",
240
391
  "",
241
- `Draft the updated master docs for ${input.projectName} as of ${input.tag}, folding in what changed across the log entries above into the current master docs. This is the official record, reviewed by a human before merging, so write it as durable reference documentation, not a changelog.`
392
+ `Draft the updated canonical docs for ${input.projectName} as of ${input.tag}, folding in what changed across the log entries above into the current canonical docs. This is the official record, reviewed by a human before merging, so write it as durable reference documentation, not a changelog.`
242
393
  ].join("\n");
243
394
  }
244
395
 
@@ -351,7 +502,7 @@ function releaseBranchName(projectName, tag) {
351
502
  return `devkeep/${projectName}/${tag}`;
352
503
  }
353
504
  async function consolidateRelease({ projectOctokit, vaultOctokit, claude, projectOwner, projectRepoName, vaultOwner, vaultRepoName, projectName, tag }) {
354
- const [{ logEntries, previousTag }, masterDocs, masterDocsSha] = await Promise.all([
505
+ const [{ logEntries, previousTag }, canonicalDocs, canonicalDocsSha] = await Promise.all([
355
506
  selectLogEntriesSincePreviousRelease({
356
507
  vaultOctokit,
357
508
  projectOctokit,
@@ -362,8 +513,8 @@ async function consolidateRelease({ projectOctokit, vaultOctokit, claude, projec
362
513
  projectRepoName,
363
514
  tag
364
515
  }),
365
- getMasterDocs(vaultOctokit, { owner: vaultOwner, repo: vaultRepoName, projectName }),
366
- getMasterDocsSha(vaultOctokit, { owner: vaultOwner, repo: vaultRepoName, projectName })
516
+ getCanonicalDocs(vaultOctokit, { owner: vaultOwner, repo: vaultRepoName, projectName }),
517
+ getCanonicalDocsSha(vaultOctokit, { owner: vaultOwner, repo: vaultRepoName, projectName })
367
518
  ]);
368
519
  const prompt = buildConsolidationPrompt({
369
520
  projectName,
@@ -371,7 +522,7 @@ async function consolidateRelease({ projectOctokit, vaultOctokit, claude, projec
371
522
  previousTag,
372
523
  date: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10),
373
524
  logEntries,
374
- masterDocs
525
+ canonicalDocs
375
526
  });
376
527
  const { text: draftedDocs, usage } = await createCompleteMessage(claude, {
377
528
  model: defaultModelFor(MODEL_TIER),
@@ -399,13 +550,13 @@ async function consolidateRelease({ projectOctokit, vaultOctokit, claude, projec
399
550
  await vaultOctokit.rest.repos.createOrUpdateFileContents({
400
551
  owner: vaultOwner,
401
552
  repo: vaultRepoName,
402
- path: masterDocsPath(projectName),
553
+ path: canonicalDocsPath(projectName),
403
554
  message: `Consolidate release: ${projectName}@${tag}`,
404
555
  content: Buffer.from(draftedDocs, "utf-8").toString("base64"),
405
556
  branch,
406
- sha: masterDocsSha
557
+ sha: canonicalDocsSha
407
558
  });
408
- await recordUsage(vaultOctokit, { owner: vaultOwner, repo: vaultRepoName, projectName, branch }, "consolidateRelease", usage);
559
+ await recordUsage(vaultOctokit, { owner: vaultOwner, repo: vaultRepoName, projectName, branch: VAULT_BASE_BRANCH }, "consolidateRelease", usage);
409
560
  const openPrs = await vaultOctokit.rest.pulls.list({ owner: vaultOwner, repo: vaultRepoName, state: "open", head: `${vaultOwner}:${branch}` }).catch(() => ({ data: [] }));
410
561
  if (openPrs.data.length > 0) {
411
562
  return { branch, prNumber: openPrs.data[0].number, prUrl: openPrs.data[0].html_url, usage };
@@ -416,7 +567,7 @@ async function consolidateRelease({ projectOctokit, vaultOctokit, claude, projec
416
567
  title: `Consolidate release: ${projectName}@${tag}`,
417
568
  head: branch,
418
569
  base: VAULT_BASE_BRANCH,
419
- body: `Drafted master doc update for \`${projectName}\` at \`${tag}\`, generated from the log entries since the last release. Review before merging \u2014 this updates the official docs.`
570
+ body: `Drafted canonical doc update for \`${projectName}\` at \`${tag}\`, generated from the log entries since the last release. Review before merging \u2014 this updates the official docs.`
420
571
  });
421
572
  return { branch, prNumber: pr.data.number, prUrl: pr.data.html_url, usage };
422
573
  }
@@ -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,
@@ -237,19 +388,19 @@ async function getProjectDiff(octokit, { owner, repo, base, head }) {
237
388
  }
238
389
  }
239
390
 
240
- // ../../modules/devlore/dist/masterDocs.js
241
- function masterDocsPath(projectName) {
242
- return `projects/${projectName}/master/README.md`;
391
+ // ../../modules/devlore/dist/canonicalDocs.js
392
+ function canonicalDocsPath(projectName) {
393
+ return `projects/${projectName}/canonical/README.md`;
243
394
  }
244
- async function getMasterDocs(octokit, params) {
245
- return (await getMasterDocsFile(octokit, params)).content;
395
+ async function getCanonicalDocs(octokit, params) {
396
+ return (await getCanonicalDocsFile(octokit, params)).content;
246
397
  }
247
- async function getMasterDocsFile(octokit, { owner, repo, projectName }) {
398
+ async function getCanonicalDocsFile(octokit, { owner, repo, projectName }) {
248
399
  try {
249
400
  const { data } = await octokit.rest.repos.getContent({
250
401
  owner,
251
402
  repo,
252
- path: masterDocsPath(projectName)
403
+ path: canonicalDocsPath(projectName)
253
404
  });
254
405
  if (Array.isArray(data) || data.type !== "file") {
255
406
  return { content: "" };
@@ -367,8 +518,8 @@ ${file.patch}`).join("\n\n");
367
518
  "Commit messages:",
368
519
  input.commitMessages.map((message) => `- ${message}`).join("\n"),
369
520
  "",
370
- "Current master docs (may be empty if none exist yet):",
371
- input.masterDocs || "(none yet)",
521
+ "Current canonical docs (may be empty if none exist yet):",
522
+ input.canonicalDocs || "(none yet)",
372
523
  "",
373
524
  "Diff:",
374
525
  diff,
@@ -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,18 +697,18 @@ 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
  });
574
704
  }
575
- async function draftBucketLogEntry({ vaultOctokit, claude, vaultOwner, vaultRepoName, projectName, headSha, date, commitMessages, files, masterDocs, moduleId, moduleName }) {
705
+ async function draftBucketLogEntry({ vaultOctokit, claude, vaultOwner, vaultRepoName, projectName, headSha, date, commitMessages, files, canonicalDocs, moduleId, moduleName }) {
576
706
  const prompt = buildLogEntryPrompt({
577
707
  projectName,
578
708
  date,
579
709
  commitMessages,
580
710
  files,
581
- masterDocs,
711
+ canonicalDocs,
582
712
  moduleName: moduleName ?? void 0
583
713
  });
584
714
  const { text, usage } = await createCompleteMessage(claude, {
@@ -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 };
@@ -633,7 +763,7 @@ async function draftLogEntry({ projectOctokit, vaultOctokit, claude, projectOwne
633
763
  ref: headSha
634
764
  });
635
765
  const buckets = routeDiffByModule(diff.files, manifest);
636
- const masterDocs = await getMasterDocs(vaultOctokit, {
766
+ const canonicalDocs = await getCanonicalDocs(vaultOctokit, {
637
767
  owner: vaultOwner,
638
768
  repo: vaultRepoName,
639
769
  projectName
@@ -651,7 +781,7 @@ async function draftLogEntry({ projectOctokit, vaultOctokit, claude, projectOwne
651
781
  date,
652
782
  commitMessages: diff.commitMessages,
653
783
  files: bucket.files,
654
- masterDocs,
784
+ canonicalDocs,
655
785
  moduleId: bucket.moduleId,
656
786
  moduleName: bucket.moduleName
657
787
  });
@@ -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`;
@@ -168,8 +319,8 @@ function buildOnboardingGuidePrompt(input) {
168
319
  "",
169
320
  input.currentState ? `--- Current state ---
170
321
  ${input.currentState}` : "",
171
- input.masterDocs ? `--- Master docs ---
172
- ${input.masterDocs}` : "",
322
+ input.canonicalDocs ? `--- Canonical docs ---
323
+ ${input.canonicalDocs}` : "",
173
324
  input.decisions.length > 0 ? `--- Decisions ---
174
325
  ${input.decisions.join("\n\n")}` : "",
175
326
  "",
@@ -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,
@@ -272,19 +400,19 @@ function isNotFound2(error) {
272
400
  return typeof error === "object" && error !== null && "status" in error && error.status === 404;
273
401
  }
274
402
 
275
- // ../../modules/devlore/dist/masterDocs.js
276
- function masterDocsPath(projectName) {
277
- return `projects/${projectName}/master/README.md`;
403
+ // ../../modules/devlore/dist/canonicalDocs.js
404
+ function canonicalDocsPath(projectName) {
405
+ return `projects/${projectName}/canonical/README.md`;
278
406
  }
279
- async function getMasterDocs(octokit, params) {
280
- return (await getMasterDocsFile(octokit, params)).content;
407
+ async function getCanonicalDocs(octokit, params) {
408
+ return (await getCanonicalDocsFile(octokit, params)).content;
281
409
  }
282
- async function getMasterDocsFile(octokit, { owner, repo, projectName }) {
410
+ async function getCanonicalDocsFile(octokit, { owner, repo, projectName }) {
283
411
  try {
284
412
  const { data } = await octokit.rest.repos.getContent({
285
413
  owner,
286
414
  repo,
287
- path: masterDocsPath(projectName)
415
+ path: canonicalDocsPath(projectName)
288
416
  });
289
417
  if (Array.isArray(data) || data.type !== "file") {
290
418
  return { content: "" };
@@ -358,21 +486,21 @@ 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") : "";
365
493
  }
366
494
  async function syncOnboarding({ projectOctokit, vaultOctokit, claude, projectOwner, projectRepoName, vaultOwner, vaultRepoName, projectName }) {
367
- const [currentState, masterDocs, entries] = await Promise.all([
495
+ const [currentState, canonicalDocs, entries] = await Promise.all([
368
496
  readVaultFile(vaultOctokit, vaultOwner, vaultRepoName, currentStatePath(projectName)),
369
- getMasterDocs(vaultOctokit, { owner: vaultOwner, repo: vaultRepoName, projectName }).catch(() => ""),
497
+ getCanonicalDocs(vaultOctokit, { owner: vaultOwner, repo: vaultRepoName, projectName }).catch(() => ""),
370
498
  listDecisionEntries(vaultOctokit, { owner: vaultOwner, repo: vaultRepoName, projectName }).catch(() => [])
371
499
  ]);
372
500
  const prompt = buildOnboardingGuidePrompt({
373
501
  projectName,
374
502
  currentState: redactText(currentState),
375
- masterDocs: redactText(masterDocs),
503
+ canonicalDocs: redactText(canonicalDocs),
376
504
  decisions: entries.map((entry) => redactText(entry.content))
377
505
  });
378
506
  const { text, usage } = await createCompleteMessage(claude, { model: defaultModelFor(MODEL_TIER), max_tokens: MAX_TOKENS, messages: [{ role: "user", content: prompt }] }, { maxContinuations: MAX_CONTINUATIONS });
@@ -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.0",
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",