@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.
- package/dist/consolidate-release.js +174 -26
- package/dist/crypting-candidates-scan.js +156 -8
- package/dist/draft-log-entry.js +172 -45
- package/dist/draft-release-notes.js +157 -32
- package/dist/sync-onboarding.js +169 -44
- package/package.json +1 -1
|
@@ -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/
|
|
194
|
-
function
|
|
195
|
-
return `projects/${projectName}/
|
|
344
|
+
// ../../modules/devlore/dist/canonicalDocs.js
|
|
345
|
+
function canonicalDocsPath(projectName) {
|
|
346
|
+
return `projects/${projectName}/canonical/README.md`;
|
|
196
347
|
}
|
|
197
|
-
async function
|
|
198
|
-
return (await
|
|
348
|
+
async function getCanonicalDocs(octokit, params) {
|
|
349
|
+
return (await getCanonicalDocsFile(octokit, params)).content;
|
|
199
350
|
}
|
|
200
|
-
async function
|
|
201
|
-
return (await
|
|
351
|
+
async function getCanonicalDocsSha(octokit, params) {
|
|
352
|
+
return (await getCanonicalDocsFile(octokit, params)).sha;
|
|
202
353
|
}
|
|
203
|
-
async function
|
|
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:
|
|
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
|
|
236
|
-
input.
|
|
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
|
|
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 },
|
|
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
|
-
|
|
366
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
74
|
-
var SECRET_ASSIGNMENT_PATTERN = new RegExp(`(?:[A-Za-z0-9]+[_-])*${
|
|
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
|
|
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 =
|
|
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
|
|
364
|
-
console.error(error);
|
|
365
|
-
process.exit(1);
|
|
366
|
-
});
|
|
514
|
+
void runCiEntrypoint("cryptingCandidatesScan", main);
|
|
367
515
|
}
|
|
368
516
|
export {
|
|
369
517
|
main,
|
package/dist/draft-log-entry.js
CHANGED
|
@@ -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/
|
|
241
|
-
function
|
|
242
|
-
return `projects/${projectName}/
|
|
391
|
+
// ../../modules/devlore/dist/canonicalDocs.js
|
|
392
|
+
function canonicalDocsPath(projectName) {
|
|
393
|
+
return `projects/${projectName}/canonical/README.md`;
|
|
243
394
|
}
|
|
244
|
-
async function
|
|
245
|
-
return (await
|
|
395
|
+
async function getCanonicalDocs(octokit, params) {
|
|
396
|
+
return (await getCanonicalDocsFile(octokit, params)).content;
|
|
246
397
|
}
|
|
247
|
-
async function
|
|
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:
|
|
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
|
|
371
|
-
input.
|
|
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
|
|
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:
|
|
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,
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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 =
|
|
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
|
|
444
|
-
console.error(error);
|
|
445
|
-
process.exit(1);
|
|
446
|
-
});
|
|
571
|
+
void runCiEntrypoint("releaseNotes", main);
|
|
447
572
|
}
|
|
448
573
|
export {
|
|
449
574
|
main,
|
package/dist/sync-onboarding.js
CHANGED
|
@@ -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.
|
|
172
|
-
${input.
|
|
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/
|
|
276
|
-
function
|
|
277
|
-
return `projects/${projectName}/
|
|
403
|
+
// ../../modules/devlore/dist/canonicalDocs.js
|
|
404
|
+
function canonicalDocsPath(projectName) {
|
|
405
|
+
return `projects/${projectName}/canonical/README.md`;
|
|
278
406
|
}
|
|
279
|
-
async function
|
|
280
|
-
return (await
|
|
407
|
+
async function getCanonicalDocs(octokit, params) {
|
|
408
|
+
return (await getCanonicalDocsFile(octokit, params)).content;
|
|
281
409
|
}
|
|
282
|
-
async function
|
|
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:
|
|
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
|
|
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,
|
|
495
|
+
const [currentState, canonicalDocs, entries] = await Promise.all([
|
|
368
496
|
readVaultFile(vaultOctokit, vaultOwner, vaultRepoName, currentStatePath(projectName)),
|
|
369
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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 =
|
|
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
|
|
452
|
-
console.error(error);
|
|
453
|
-
process.exit(1);
|
|
454
|
-
});
|
|
579
|
+
void runCiEntrypoint("syncOnboarding", main);
|
|
455
580
|
}
|
|
456
581
|
export {
|
|
457
582
|
main,
|