@starterculture/devkeep-actions 0.5.1 → 0.5.2
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 +60 -9
- package/dist/crypting-candidates-scan.js +6 -1
- package/dist/draft-log-entry.js +60 -9
- package/dist/draft-release-notes.js +83 -32
- package/dist/sync-onboarding.js +132 -71
- package/package.json +1 -1
|
@@ -25,9 +25,41 @@ async function retryOnConflict(attempt, maxAttempts = 3) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
// ../../modules/core/dist/tokenUsage.js
|
|
28
|
+
function usageForModel(model, tokens) {
|
|
29
|
+
return { ...tokens, byModel: { [model]: tokens } };
|
|
30
|
+
}
|
|
28
31
|
var ZERO_USAGE = { inputTokens: 0, outputTokens: 0 };
|
|
29
32
|
function addTokenUsage(a, b) {
|
|
30
|
-
|
|
33
|
+
const cacheReadTokens = (a.cacheReadTokens ?? 0) + (b.cacheReadTokens ?? 0);
|
|
34
|
+
const cacheWriteTokens = (a.cacheWriteTokens ?? 0) + (b.cacheWriteTokens ?? 0);
|
|
35
|
+
const byModel = mergeByModel(a.byModel, b.byModel);
|
|
36
|
+
return {
|
|
37
|
+
inputTokens: a.inputTokens + b.inputTokens,
|
|
38
|
+
outputTokens: a.outputTokens + b.outputTokens,
|
|
39
|
+
// Kept absent rather than zero when nothing was cached, so a ledger
|
|
40
|
+
// entry from before caching and one from a run that cached nothing
|
|
41
|
+
// look the same.
|
|
42
|
+
...cacheReadTokens > 0 ? { cacheReadTokens } : {},
|
|
43
|
+
...cacheWriteTokens > 0 ? { cacheWriteTokens } : {},
|
|
44
|
+
// Absent for the same reason: a record with no model attribution and
|
|
45
|
+
// one with an empty map should not read differently.
|
|
46
|
+
...byModel ? { byModel } : {}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function mergeByModel(a, b) {
|
|
50
|
+
if (!a && !b)
|
|
51
|
+
return void 0;
|
|
52
|
+
const merged = { ...a };
|
|
53
|
+
for (const [model, tokens] of Object.entries(b ?? {})) {
|
|
54
|
+
const existing = merged[model];
|
|
55
|
+
merged[model] = existing ? {
|
|
56
|
+
inputTokens: existing.inputTokens + tokens.inputTokens,
|
|
57
|
+
outputTokens: existing.outputTokens + tokens.outputTokens,
|
|
58
|
+
...(existing.cacheReadTokens ?? 0) + (tokens.cacheReadTokens ?? 0) > 0 ? { cacheReadTokens: (existing.cacheReadTokens ?? 0) + (tokens.cacheReadTokens ?? 0) } : {},
|
|
59
|
+
...(existing.cacheWriteTokens ?? 0) + (tokens.cacheWriteTokens ?? 0) > 0 ? { cacheWriteTokens: (existing.cacheWriteTokens ?? 0) + (tokens.cacheWriteTokens ?? 0) } : {}
|
|
60
|
+
} : tokens;
|
|
61
|
+
}
|
|
62
|
+
return merged;
|
|
31
63
|
}
|
|
32
64
|
|
|
33
65
|
// ../../modules/core/dist/rollover.js
|
|
@@ -89,10 +121,10 @@ async function createCompleteMessage(claude, params, { maxContinuations = DEFAUL
|
|
|
89
121
|
const message = requestOptions ? await claude.messages.create({ ...params, messages }, requestOptions) : await claude.messages.create({ ...params, messages });
|
|
90
122
|
const text = message.content.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
91
123
|
fullText += text;
|
|
92
|
-
usage = addTokenUsage(usage, {
|
|
124
|
+
usage = addTokenUsage(usage, usageForModel(params.model, {
|
|
93
125
|
inputTokens: message.usage?.input_tokens ?? 0,
|
|
94
126
|
outputTokens: message.usage?.output_tokens ?? 0
|
|
95
|
-
});
|
|
127
|
+
}));
|
|
96
128
|
if (message.stop_reason !== "max_tokens") {
|
|
97
129
|
return { text: fullText, usage };
|
|
98
130
|
}
|
|
@@ -104,19 +136,19 @@ async function createCompleteMessage(claude, params, { maxContinuations = DEFAUL
|
|
|
104
136
|
var MODEL_REGISTRY = {
|
|
105
137
|
fast: {
|
|
106
138
|
description: "Quick, cheap work where depth matters least.",
|
|
107
|
-
models: [{ id: "claude-haiku-4-5-20251001", label: "Haiku 4.5" }]
|
|
139
|
+
models: [{ id: "claude-haiku-4-5-20251001", label: "Haiku 4.5", contextTokens: 2e5 }]
|
|
108
140
|
},
|
|
109
141
|
balanced: {
|
|
110
142
|
description: "The everyday tier: chat, log entries, release notes, the daily digest. Frequent enough that cost matters.",
|
|
111
|
-
models: [{ id: "claude-sonnet-5", label: "Sonnet 5" }]
|
|
143
|
+
models: [{ id: "claude-sonnet-5", label: "Sonnet 5", contextTokens: 2e5 }]
|
|
112
144
|
},
|
|
113
145
|
capable: {
|
|
114
146
|
description: "Deeper reasoning for infrequent, high-value work where getting it right outweighs the cost.",
|
|
115
|
-
models: [{ id: "claude-opus-5", label: "Opus 5" }]
|
|
147
|
+
models: [{ id: "claude-opus-5", label: "Opus 5", contextTokens: 2e5 }]
|
|
116
148
|
},
|
|
117
149
|
max: {
|
|
118
150
|
description: "Maximum depth, for long-running agentic work. Opt-in \u2014 the most expensive tier.",
|
|
119
|
-
models: [{ id: "claude-fable-5", label: "Fable 5" }]
|
|
151
|
+
models: [{ id: "claude-fable-5", label: "Fable 5", contextTokens: 2e5 }]
|
|
120
152
|
}
|
|
121
153
|
};
|
|
122
154
|
function defaultModelFor(tier) {
|
|
@@ -280,7 +312,7 @@ async function runCiEntrypoint(source, main2, deps = defaultDeps) {
|
|
|
280
312
|
}
|
|
281
313
|
}
|
|
282
314
|
|
|
283
|
-
// ../../modules/
|
|
315
|
+
// ../../modules/core/dist/ciUsage.js
|
|
284
316
|
var EMPTY_PROJECT_USAGE = {
|
|
285
317
|
draftLogEntry: ZERO_USAGE,
|
|
286
318
|
consolidateRelease: ZERO_USAGE,
|
|
@@ -291,6 +323,7 @@ var EMPTY_PROJECT_USAGE = {
|
|
|
291
323
|
syncOnboarding: ZERO_USAGE,
|
|
292
324
|
releaseNotes: ZERO_USAGE,
|
|
293
325
|
captureBaseline: ZERO_USAGE,
|
|
326
|
+
devseerReview: ZERO_USAGE,
|
|
294
327
|
rollover: ZERO_ROLLOVER_USAGE
|
|
295
328
|
};
|
|
296
329
|
function usagePath(projectName) {
|
|
@@ -317,14 +350,24 @@ async function getUsageFile(octokit, { owner, repo, projectName }) {
|
|
|
317
350
|
}
|
|
318
351
|
}
|
|
319
352
|
function addUsage(current, source, delta, now = /* @__PURE__ */ new Date()) {
|
|
353
|
+
const bySource = current.rolloverBySource ?? {};
|
|
320
354
|
return {
|
|
321
355
|
...current,
|
|
322
356
|
[source]: addTokenUsage(current[source], delta),
|
|
323
|
-
|
|
357
|
+
// Both: the combined bucket stays the total it always was, and the
|
|
358
|
+
// per-source split is what lets the dashboard attribute it.
|
|
359
|
+
rollover: addRolloverUsage(current.rollover, delta, now),
|
|
360
|
+
rolloverBySource: {
|
|
361
|
+
...bySource,
|
|
362
|
+
[source]: addRolloverUsage(bySource[source] ?? ZERO_ROLLOVER_USAGE, delta, now)
|
|
363
|
+
}
|
|
324
364
|
};
|
|
325
365
|
}
|
|
326
366
|
async function recordUsage(octokit, params, source, delta, now = /* @__PURE__ */ new Date()) {
|
|
327
367
|
const { owner, repo, projectName, branch } = params;
|
|
368
|
+
if (delta.inputTokens === 0 && delta.outputTokens === 0) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
328
371
|
await retryOnConflict(async () => {
|
|
329
372
|
const { usage: current, sha } = await getUsageFile(octokit, { owner, repo, projectName });
|
|
330
373
|
const updated = addUsage(current, source, delta, now);
|
|
@@ -376,6 +419,14 @@ function isNotFound2(error) {
|
|
|
376
419
|
}
|
|
377
420
|
|
|
378
421
|
// ../../modules/devlore/dist/prompt.js
|
|
422
|
+
var SUPERSEDED_GUIDANCE = [
|
|
423
|
+
"These documents record how the thinking changed over time, so they contain claims that were later corrected, and the corrected version is still physically present.",
|
|
424
|
+
"**A correction outranks what it corrects.** Where two passages disagree, the later-dated one is what is true now. A passage struck through, marked superseded, described as decided against, or sitting under a heading that says the earlier view was wrong, records what was believed \u2014 never what is. Do not restate any of it as current."
|
|
425
|
+
].join("\n");
|
|
426
|
+
var SUPERSEDED_GUIDANCE_WITH_LESSONS = [
|
|
427
|
+
SUPERSEDED_GUIDANCE,
|
|
428
|
+
"A reversed decision is still worth telling a newcomer about \u2014 but as something the project learned, never as a rule they should follow. Say what is true now first, and mention the earlier version only where the fact that it changed is itself the lesson."
|
|
429
|
+
].join("\n");
|
|
379
430
|
function buildConsolidationPrompt(input) {
|
|
380
431
|
const entriesText = input.logEntries.map((entry) => `--- ${entry.path} ---
|
|
381
432
|
${entry.content}`).join("\n\n");
|
|
@@ -499,7 +499,12 @@ async function main(deps = defaultDeps2) {
|
|
|
499
499
|
const headSha = requireEnv(env, "HEAD_SHA");
|
|
500
500
|
const projectOctokit = deps.createGithubClient(requireEnv(env, "GITHUB_TOKEN"));
|
|
501
501
|
const vaultOctokit = deps.createGithubClient(requireEnv(env, "DEVKEEP_GITHUB_TOKEN"));
|
|
502
|
-
const
|
|
502
|
+
const rawKey = env.DEVCRYPT_BLIND_INDEX_KEY;
|
|
503
|
+
if (!rawKey) {
|
|
504
|
+
deps.log("No DEVCRYPT_BLIND_INDEX_KEY for this project \u2014 Devcrypt has not been used against this vault, so there is nothing to scan for. Re-link once it has been.");
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
const blindIndexKey = Buffer.from(rawKey, "base64");
|
|
503
508
|
const result = await deps.scanCryptingCandidates({
|
|
504
509
|
projectOctokit,
|
|
505
510
|
vaultOctokit,
|
package/dist/draft-log-entry.js
CHANGED
|
@@ -25,9 +25,41 @@ async function retryOnConflict(attempt, maxAttempts = 3) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
// ../../modules/core/dist/tokenUsage.js
|
|
28
|
+
function usageForModel(model, tokens) {
|
|
29
|
+
return { ...tokens, byModel: { [model]: tokens } };
|
|
30
|
+
}
|
|
28
31
|
var ZERO_USAGE = { inputTokens: 0, outputTokens: 0 };
|
|
29
32
|
function addTokenUsage(a, b) {
|
|
30
|
-
|
|
33
|
+
const cacheReadTokens = (a.cacheReadTokens ?? 0) + (b.cacheReadTokens ?? 0);
|
|
34
|
+
const cacheWriteTokens = (a.cacheWriteTokens ?? 0) + (b.cacheWriteTokens ?? 0);
|
|
35
|
+
const byModel = mergeByModel(a.byModel, b.byModel);
|
|
36
|
+
return {
|
|
37
|
+
inputTokens: a.inputTokens + b.inputTokens,
|
|
38
|
+
outputTokens: a.outputTokens + b.outputTokens,
|
|
39
|
+
// Kept absent rather than zero when nothing was cached, so a ledger
|
|
40
|
+
// entry from before caching and one from a run that cached nothing
|
|
41
|
+
// look the same.
|
|
42
|
+
...cacheReadTokens > 0 ? { cacheReadTokens } : {},
|
|
43
|
+
...cacheWriteTokens > 0 ? { cacheWriteTokens } : {},
|
|
44
|
+
// Absent for the same reason: a record with no model attribution and
|
|
45
|
+
// one with an empty map should not read differently.
|
|
46
|
+
...byModel ? { byModel } : {}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function mergeByModel(a, b) {
|
|
50
|
+
if (!a && !b)
|
|
51
|
+
return void 0;
|
|
52
|
+
const merged = { ...a };
|
|
53
|
+
for (const [model, tokens] of Object.entries(b ?? {})) {
|
|
54
|
+
const existing = merged[model];
|
|
55
|
+
merged[model] = existing ? {
|
|
56
|
+
inputTokens: existing.inputTokens + tokens.inputTokens,
|
|
57
|
+
outputTokens: existing.outputTokens + tokens.outputTokens,
|
|
58
|
+
...(existing.cacheReadTokens ?? 0) + (tokens.cacheReadTokens ?? 0) > 0 ? { cacheReadTokens: (existing.cacheReadTokens ?? 0) + (tokens.cacheReadTokens ?? 0) } : {},
|
|
59
|
+
...(existing.cacheWriteTokens ?? 0) + (tokens.cacheWriteTokens ?? 0) > 0 ? { cacheWriteTokens: (existing.cacheWriteTokens ?? 0) + (tokens.cacheWriteTokens ?? 0) } : {}
|
|
60
|
+
} : tokens;
|
|
61
|
+
}
|
|
62
|
+
return merged;
|
|
31
63
|
}
|
|
32
64
|
|
|
33
65
|
// ../../modules/core/dist/rollover.js
|
|
@@ -89,10 +121,10 @@ async function createCompleteMessage(claude, params, { maxContinuations = DEFAUL
|
|
|
89
121
|
const message = requestOptions ? await claude.messages.create({ ...params, messages }, requestOptions) : await claude.messages.create({ ...params, messages });
|
|
90
122
|
const text = message.content.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
91
123
|
fullText += text;
|
|
92
|
-
usage = addTokenUsage(usage, {
|
|
124
|
+
usage = addTokenUsage(usage, usageForModel(params.model, {
|
|
93
125
|
inputTokens: message.usage?.input_tokens ?? 0,
|
|
94
126
|
outputTokens: message.usage?.output_tokens ?? 0
|
|
95
|
-
});
|
|
127
|
+
}));
|
|
96
128
|
if (message.stop_reason !== "max_tokens") {
|
|
97
129
|
return { text: fullText, usage };
|
|
98
130
|
}
|
|
@@ -104,19 +136,19 @@ async function createCompleteMessage(claude, params, { maxContinuations = DEFAUL
|
|
|
104
136
|
var MODEL_REGISTRY = {
|
|
105
137
|
fast: {
|
|
106
138
|
description: "Quick, cheap work where depth matters least.",
|
|
107
|
-
models: [{ id: "claude-haiku-4-5-20251001", label: "Haiku 4.5" }]
|
|
139
|
+
models: [{ id: "claude-haiku-4-5-20251001", label: "Haiku 4.5", contextTokens: 2e5 }]
|
|
108
140
|
},
|
|
109
141
|
balanced: {
|
|
110
142
|
description: "The everyday tier: chat, log entries, release notes, the daily digest. Frequent enough that cost matters.",
|
|
111
|
-
models: [{ id: "claude-sonnet-5", label: "Sonnet 5" }]
|
|
143
|
+
models: [{ id: "claude-sonnet-5", label: "Sonnet 5", contextTokens: 2e5 }]
|
|
112
144
|
},
|
|
113
145
|
capable: {
|
|
114
146
|
description: "Deeper reasoning for infrequent, high-value work where getting it right outweighs the cost.",
|
|
115
|
-
models: [{ id: "claude-opus-5", label: "Opus 5" }]
|
|
147
|
+
models: [{ id: "claude-opus-5", label: "Opus 5", contextTokens: 2e5 }]
|
|
116
148
|
},
|
|
117
149
|
max: {
|
|
118
150
|
description: "Maximum depth, for long-running agentic work. Opt-in \u2014 the most expensive tier.",
|
|
119
|
-
models: [{ id: "claude-fable-5", label: "Fable 5" }]
|
|
151
|
+
models: [{ id: "claude-fable-5", label: "Fable 5", contextTokens: 2e5 }]
|
|
120
152
|
}
|
|
121
153
|
};
|
|
122
154
|
function defaultModelFor(tier) {
|
|
@@ -280,7 +312,7 @@ async function runCiEntrypoint(source, main2, deps = defaultDeps) {
|
|
|
280
312
|
}
|
|
281
313
|
}
|
|
282
314
|
|
|
283
|
-
// ../../modules/
|
|
315
|
+
// ../../modules/core/dist/ciUsage.js
|
|
284
316
|
var EMPTY_PROJECT_USAGE = {
|
|
285
317
|
draftLogEntry: ZERO_USAGE,
|
|
286
318
|
consolidateRelease: ZERO_USAGE,
|
|
@@ -291,6 +323,7 @@ var EMPTY_PROJECT_USAGE = {
|
|
|
291
323
|
syncOnboarding: ZERO_USAGE,
|
|
292
324
|
releaseNotes: ZERO_USAGE,
|
|
293
325
|
captureBaseline: ZERO_USAGE,
|
|
326
|
+
devseerReview: ZERO_USAGE,
|
|
294
327
|
rollover: ZERO_ROLLOVER_USAGE
|
|
295
328
|
};
|
|
296
329
|
function usagePath(projectName) {
|
|
@@ -317,14 +350,24 @@ async function getUsageFile(octokit, { owner, repo, projectName }) {
|
|
|
317
350
|
}
|
|
318
351
|
}
|
|
319
352
|
function addUsage(current, source, delta, now = /* @__PURE__ */ new Date()) {
|
|
353
|
+
const bySource = current.rolloverBySource ?? {};
|
|
320
354
|
return {
|
|
321
355
|
...current,
|
|
322
356
|
[source]: addTokenUsage(current[source], delta),
|
|
323
|
-
|
|
357
|
+
// Both: the combined bucket stays the total it always was, and the
|
|
358
|
+
// per-source split is what lets the dashboard attribute it.
|
|
359
|
+
rollover: addRolloverUsage(current.rollover, delta, now),
|
|
360
|
+
rolloverBySource: {
|
|
361
|
+
...bySource,
|
|
362
|
+
[source]: addRolloverUsage(bySource[source] ?? ZERO_ROLLOVER_USAGE, delta, now)
|
|
363
|
+
}
|
|
324
364
|
};
|
|
325
365
|
}
|
|
326
366
|
async function recordUsage(octokit, params, source, delta, now = /* @__PURE__ */ new Date()) {
|
|
327
367
|
const { owner, repo, projectName, branch } = params;
|
|
368
|
+
if (delta.inputTokens === 0 && delta.outputTokens === 0) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
328
371
|
await retryOnConflict(async () => {
|
|
329
372
|
const { usage: current, sha } = await getUsageFile(octokit, { owner, repo, projectName });
|
|
330
373
|
const updated = addUsage(current, source, delta, now);
|
|
@@ -503,6 +546,14 @@ function isNotFound3(error) {
|
|
|
503
546
|
|
|
504
547
|
// ../../modules/devlore/dist/prompt.js
|
|
505
548
|
var MAX_DIFF_CHARS = 2e4;
|
|
549
|
+
var SUPERSEDED_GUIDANCE = [
|
|
550
|
+
"These documents record how the thinking changed over time, so they contain claims that were later corrected, and the corrected version is still physically present.",
|
|
551
|
+
"**A correction outranks what it corrects.** Where two passages disagree, the later-dated one is what is true now. A passage struck through, marked superseded, described as decided against, or sitting under a heading that says the earlier view was wrong, records what was believed \u2014 never what is. Do not restate any of it as current."
|
|
552
|
+
].join("\n");
|
|
553
|
+
var SUPERSEDED_GUIDANCE_WITH_LESSONS = [
|
|
554
|
+
SUPERSEDED_GUIDANCE,
|
|
555
|
+
"A reversed decision is still worth telling a newcomer about \u2014 but as something the project learned, never as a rule they should follow. Say what is true now first, and mention the earlier version only where the fact that it changed is itself the lesson."
|
|
556
|
+
].join("\n");
|
|
506
557
|
function buildLogEntryPrompt(input) {
|
|
507
558
|
const diffText = input.files.map((file) => `--- ${file.filename} (${file.status}) ---
|
|
508
559
|
${file.patch}`).join("\n\n");
|
|
@@ -25,9 +25,41 @@ async function retryOnConflict(attempt, maxAttempts = 3) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
// ../../modules/core/dist/tokenUsage.js
|
|
28
|
+
function usageForModel(model, tokens) {
|
|
29
|
+
return { ...tokens, byModel: { [model]: tokens } };
|
|
30
|
+
}
|
|
28
31
|
var ZERO_USAGE = { inputTokens: 0, outputTokens: 0 };
|
|
29
32
|
function addTokenUsage(a, b) {
|
|
30
|
-
|
|
33
|
+
const cacheReadTokens = (a.cacheReadTokens ?? 0) + (b.cacheReadTokens ?? 0);
|
|
34
|
+
const cacheWriteTokens = (a.cacheWriteTokens ?? 0) + (b.cacheWriteTokens ?? 0);
|
|
35
|
+
const byModel = mergeByModel(a.byModel, b.byModel);
|
|
36
|
+
return {
|
|
37
|
+
inputTokens: a.inputTokens + b.inputTokens,
|
|
38
|
+
outputTokens: a.outputTokens + b.outputTokens,
|
|
39
|
+
// Kept absent rather than zero when nothing was cached, so a ledger
|
|
40
|
+
// entry from before caching and one from a run that cached nothing
|
|
41
|
+
// look the same.
|
|
42
|
+
...cacheReadTokens > 0 ? { cacheReadTokens } : {},
|
|
43
|
+
...cacheWriteTokens > 0 ? { cacheWriteTokens } : {},
|
|
44
|
+
// Absent for the same reason: a record with no model attribution and
|
|
45
|
+
// one with an empty map should not read differently.
|
|
46
|
+
...byModel ? { byModel } : {}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function mergeByModel(a, b) {
|
|
50
|
+
if (!a && !b)
|
|
51
|
+
return void 0;
|
|
52
|
+
const merged = { ...a };
|
|
53
|
+
for (const [model, tokens] of Object.entries(b ?? {})) {
|
|
54
|
+
const existing = merged[model];
|
|
55
|
+
merged[model] = existing ? {
|
|
56
|
+
inputTokens: existing.inputTokens + tokens.inputTokens,
|
|
57
|
+
outputTokens: existing.outputTokens + tokens.outputTokens,
|
|
58
|
+
...(existing.cacheReadTokens ?? 0) + (tokens.cacheReadTokens ?? 0) > 0 ? { cacheReadTokens: (existing.cacheReadTokens ?? 0) + (tokens.cacheReadTokens ?? 0) } : {},
|
|
59
|
+
...(existing.cacheWriteTokens ?? 0) + (tokens.cacheWriteTokens ?? 0) > 0 ? { cacheWriteTokens: (existing.cacheWriteTokens ?? 0) + (tokens.cacheWriteTokens ?? 0) } : {}
|
|
60
|
+
} : tokens;
|
|
61
|
+
}
|
|
62
|
+
return merged;
|
|
31
63
|
}
|
|
32
64
|
|
|
33
65
|
// ../../modules/core/dist/rollover.js
|
|
@@ -89,10 +121,10 @@ async function createCompleteMessage(claude, params, { maxContinuations = DEFAUL
|
|
|
89
121
|
const message = requestOptions ? await claude.messages.create({ ...params, messages }, requestOptions) : await claude.messages.create({ ...params, messages });
|
|
90
122
|
const text = message.content.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
91
123
|
fullText += text;
|
|
92
|
-
usage = addTokenUsage(usage, {
|
|
124
|
+
usage = addTokenUsage(usage, usageForModel(params.model, {
|
|
93
125
|
inputTokens: message.usage?.input_tokens ?? 0,
|
|
94
126
|
outputTokens: message.usage?.output_tokens ?? 0
|
|
95
|
-
});
|
|
127
|
+
}));
|
|
96
128
|
if (message.stop_reason !== "max_tokens") {
|
|
97
129
|
return { text: fullText, usage };
|
|
98
130
|
}
|
|
@@ -104,19 +136,19 @@ async function createCompleteMessage(claude, params, { maxContinuations = DEFAUL
|
|
|
104
136
|
var MODEL_REGISTRY = {
|
|
105
137
|
fast: {
|
|
106
138
|
description: "Quick, cheap work where depth matters least.",
|
|
107
|
-
models: [{ id: "claude-haiku-4-5-20251001", label: "Haiku 4.5" }]
|
|
139
|
+
models: [{ id: "claude-haiku-4-5-20251001", label: "Haiku 4.5", contextTokens: 2e5 }]
|
|
108
140
|
},
|
|
109
141
|
balanced: {
|
|
110
142
|
description: "The everyday tier: chat, log entries, release notes, the daily digest. Frequent enough that cost matters.",
|
|
111
|
-
models: [{ id: "claude-sonnet-5", label: "Sonnet 5" }]
|
|
143
|
+
models: [{ id: "claude-sonnet-5", label: "Sonnet 5", contextTokens: 2e5 }]
|
|
112
144
|
},
|
|
113
145
|
capable: {
|
|
114
146
|
description: "Deeper reasoning for infrequent, high-value work where getting it right outweighs the cost.",
|
|
115
|
-
models: [{ id: "claude-opus-5", label: "Opus 5" }]
|
|
147
|
+
models: [{ id: "claude-opus-5", label: "Opus 5", contextTokens: 2e5 }]
|
|
116
148
|
},
|
|
117
149
|
max: {
|
|
118
150
|
description: "Maximum depth, for long-running agentic work. Opt-in \u2014 the most expensive tier.",
|
|
119
|
-
models: [{ id: "claude-fable-5", label: "Fable 5" }]
|
|
151
|
+
models: [{ id: "claude-fable-5", label: "Fable 5", contextTokens: 2e5 }]
|
|
120
152
|
}
|
|
121
153
|
};
|
|
122
154
|
function defaultModelFor(tier) {
|
|
@@ -280,30 +312,7 @@ async function runCiEntrypoint(source, main2, deps = defaultDeps) {
|
|
|
280
312
|
}
|
|
281
313
|
}
|
|
282
314
|
|
|
283
|
-
// ../../modules/
|
|
284
|
-
function buildReleaseNotesPrompt(input) {
|
|
285
|
-
return [
|
|
286
|
-
`Project: ${input.projectName}`,
|
|
287
|
-
`Release: ${input.tag}`,
|
|
288
|
-
input.previousTag ? `Previous release: ${input.previousTag}. The entries below are the work done since then.` : "This is the first release, so the entries below cover everything so far.",
|
|
289
|
-
"",
|
|
290
|
-
"Below are the decision log entries Devkeep recorded for the work in this release.",
|
|
291
|
-
"",
|
|
292
|
-
...input.logEntries,
|
|
293
|
-
"",
|
|
294
|
-
"Write release notes for the people who use this product. Lead with what they can now do that they could not before, and what changed in behaviour they will notice. Group related changes rather than listing entries one by one.",
|
|
295
|
-
"",
|
|
296
|
-
"Plain language, not commit-history jargon: no file paths, no function names, no internal module names unless the user genuinely interacts with them by name.",
|
|
297
|
-
"",
|
|
298
|
-
'If this release contains nothing user-facing \u2014 internal refactoring, tooling, workflow changes \u2014 say exactly that in a sentence or two. Do not manufacture user-facing significance that is not there; an honest "this release contains internal changes only" is the correct output, and inventing a feature to fill the page is worse than a short note.',
|
|
299
|
-
"",
|
|
300
|
-
"Do NOT include a top-level heading \u2014 one is added automatically. Start directly with the content.",
|
|
301
|
-
"",
|
|
302
|
-
"Return the notes only \u2014 no preamble, no meta-commentary."
|
|
303
|
-
].join("\n");
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// ../../modules/devlore/dist/ciUsage.js
|
|
315
|
+
// ../../modules/core/dist/ciUsage.js
|
|
307
316
|
var EMPTY_PROJECT_USAGE = {
|
|
308
317
|
draftLogEntry: ZERO_USAGE,
|
|
309
318
|
consolidateRelease: ZERO_USAGE,
|
|
@@ -314,6 +323,7 @@ var EMPTY_PROJECT_USAGE = {
|
|
|
314
323
|
syncOnboarding: ZERO_USAGE,
|
|
315
324
|
releaseNotes: ZERO_USAGE,
|
|
316
325
|
captureBaseline: ZERO_USAGE,
|
|
326
|
+
devseerReview: ZERO_USAGE,
|
|
317
327
|
rollover: ZERO_ROLLOVER_USAGE
|
|
318
328
|
};
|
|
319
329
|
function usagePath(projectName) {
|
|
@@ -340,14 +350,24 @@ async function getUsageFile(octokit, { owner, repo, projectName }) {
|
|
|
340
350
|
}
|
|
341
351
|
}
|
|
342
352
|
function addUsage(current, source, delta, now = /* @__PURE__ */ new Date()) {
|
|
353
|
+
const bySource = current.rolloverBySource ?? {};
|
|
343
354
|
return {
|
|
344
355
|
...current,
|
|
345
356
|
[source]: addTokenUsage(current[source], delta),
|
|
346
|
-
|
|
357
|
+
// Both: the combined bucket stays the total it always was, and the
|
|
358
|
+
// per-source split is what lets the dashboard attribute it.
|
|
359
|
+
rollover: addRolloverUsage(current.rollover, delta, now),
|
|
360
|
+
rolloverBySource: {
|
|
361
|
+
...bySource,
|
|
362
|
+
[source]: addRolloverUsage(bySource[source] ?? ZERO_ROLLOVER_USAGE, delta, now)
|
|
363
|
+
}
|
|
347
364
|
};
|
|
348
365
|
}
|
|
349
366
|
async function recordUsage(octokit, params, source, delta, now = /* @__PURE__ */ new Date()) {
|
|
350
367
|
const { owner, repo, projectName, branch } = params;
|
|
368
|
+
if (delta.inputTokens === 0 && delta.outputTokens === 0) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
351
371
|
await retryOnConflict(async () => {
|
|
352
372
|
const { usage: current, sha } = await getUsageFile(octokit, { owner, repo, projectName });
|
|
353
373
|
const updated = addUsage(current, source, delta, now);
|
|
@@ -366,6 +386,37 @@ function isNotFound(error) {
|
|
|
366
386
|
return typeof error === "object" && error !== null && "status" in error && error.status === 404;
|
|
367
387
|
}
|
|
368
388
|
|
|
389
|
+
// ../../modules/devlore/dist/prompt.js
|
|
390
|
+
var SUPERSEDED_GUIDANCE = [
|
|
391
|
+
"These documents record how the thinking changed over time, so they contain claims that were later corrected, and the corrected version is still physically present.",
|
|
392
|
+
"**A correction outranks what it corrects.** Where two passages disagree, the later-dated one is what is true now. A passage struck through, marked superseded, described as decided against, or sitting under a heading that says the earlier view was wrong, records what was believed \u2014 never what is. Do not restate any of it as current."
|
|
393
|
+
].join("\n");
|
|
394
|
+
var SUPERSEDED_GUIDANCE_WITH_LESSONS = [
|
|
395
|
+
SUPERSEDED_GUIDANCE,
|
|
396
|
+
"A reversed decision is still worth telling a newcomer about \u2014 but as something the project learned, never as a rule they should follow. Say what is true now first, and mention the earlier version only where the fact that it changed is itself the lesson."
|
|
397
|
+
].join("\n");
|
|
398
|
+
function buildReleaseNotesPrompt(input) {
|
|
399
|
+
return [
|
|
400
|
+
`Project: ${input.projectName}`,
|
|
401
|
+
`Release: ${input.tag}`,
|
|
402
|
+
input.previousTag ? `Previous release: ${input.previousTag}. The entries below are the work done since then.` : "This is the first release, so the entries below cover everything so far.",
|
|
403
|
+
"",
|
|
404
|
+
"Below are the decision log entries Devkeep recorded for the work in this release.",
|
|
405
|
+
"",
|
|
406
|
+
...input.logEntries,
|
|
407
|
+
"",
|
|
408
|
+
"Write release notes for the people who use this product. Lead with what they can now do that they could not before, and what changed in behaviour they will notice. Group related changes rather than listing entries one by one.",
|
|
409
|
+
"",
|
|
410
|
+
"Plain language, not commit-history jargon: no file paths, no function names, no internal module names unless the user genuinely interacts with them by name.",
|
|
411
|
+
"",
|
|
412
|
+
'If this release contains nothing user-facing \u2014 internal refactoring, tooling, workflow changes \u2014 say exactly that in a sentence or two. Do not manufacture user-facing significance that is not there; an honest "this release contains internal changes only" is the correct output, and inventing a feature to fill the page is worse than a short note.',
|
|
413
|
+
"",
|
|
414
|
+
"Do NOT include a top-level heading \u2014 one is added automatically. Start directly with the content.",
|
|
415
|
+
"",
|
|
416
|
+
"Return the notes only \u2014 no preamble, no meta-commentary."
|
|
417
|
+
].join("\n");
|
|
418
|
+
}
|
|
419
|
+
|
|
369
420
|
// ../../modules/devlore/dist/logEntries.js
|
|
370
421
|
function logEntriesDir(projectName) {
|
|
371
422
|
return `projects/${projectName}/log`;
|
package/dist/sync-onboarding.js
CHANGED
|
@@ -25,9 +25,41 @@ async function retryOnConflict(attempt, maxAttempts = 3) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
// ../../modules/core/dist/tokenUsage.js
|
|
28
|
+
function usageForModel(model, tokens) {
|
|
29
|
+
return { ...tokens, byModel: { [model]: tokens } };
|
|
30
|
+
}
|
|
28
31
|
var ZERO_USAGE = { inputTokens: 0, outputTokens: 0 };
|
|
29
32
|
function addTokenUsage(a, b) {
|
|
30
|
-
|
|
33
|
+
const cacheReadTokens = (a.cacheReadTokens ?? 0) + (b.cacheReadTokens ?? 0);
|
|
34
|
+
const cacheWriteTokens = (a.cacheWriteTokens ?? 0) + (b.cacheWriteTokens ?? 0);
|
|
35
|
+
const byModel = mergeByModel(a.byModel, b.byModel);
|
|
36
|
+
return {
|
|
37
|
+
inputTokens: a.inputTokens + b.inputTokens,
|
|
38
|
+
outputTokens: a.outputTokens + b.outputTokens,
|
|
39
|
+
// Kept absent rather than zero when nothing was cached, so a ledger
|
|
40
|
+
// entry from before caching and one from a run that cached nothing
|
|
41
|
+
// look the same.
|
|
42
|
+
...cacheReadTokens > 0 ? { cacheReadTokens } : {},
|
|
43
|
+
...cacheWriteTokens > 0 ? { cacheWriteTokens } : {},
|
|
44
|
+
// Absent for the same reason: a record with no model attribution and
|
|
45
|
+
// one with an empty map should not read differently.
|
|
46
|
+
...byModel ? { byModel } : {}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function mergeByModel(a, b) {
|
|
50
|
+
if (!a && !b)
|
|
51
|
+
return void 0;
|
|
52
|
+
const merged = { ...a };
|
|
53
|
+
for (const [model, tokens] of Object.entries(b ?? {})) {
|
|
54
|
+
const existing = merged[model];
|
|
55
|
+
merged[model] = existing ? {
|
|
56
|
+
inputTokens: existing.inputTokens + tokens.inputTokens,
|
|
57
|
+
outputTokens: existing.outputTokens + tokens.outputTokens,
|
|
58
|
+
...(existing.cacheReadTokens ?? 0) + (tokens.cacheReadTokens ?? 0) > 0 ? { cacheReadTokens: (existing.cacheReadTokens ?? 0) + (tokens.cacheReadTokens ?? 0) } : {},
|
|
59
|
+
...(existing.cacheWriteTokens ?? 0) + (tokens.cacheWriteTokens ?? 0) > 0 ? { cacheWriteTokens: (existing.cacheWriteTokens ?? 0) + (tokens.cacheWriteTokens ?? 0) } : {}
|
|
60
|
+
} : tokens;
|
|
61
|
+
}
|
|
62
|
+
return merged;
|
|
31
63
|
}
|
|
32
64
|
|
|
33
65
|
// ../../modules/core/dist/rollover.js
|
|
@@ -89,10 +121,10 @@ async function createCompleteMessage(claude, params, { maxContinuations = DEFAUL
|
|
|
89
121
|
const message = requestOptions ? await claude.messages.create({ ...params, messages }, requestOptions) : await claude.messages.create({ ...params, messages });
|
|
90
122
|
const text = message.content.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
91
123
|
fullText += text;
|
|
92
|
-
usage = addTokenUsage(usage, {
|
|
124
|
+
usage = addTokenUsage(usage, usageForModel(params.model, {
|
|
93
125
|
inputTokens: message.usage?.input_tokens ?? 0,
|
|
94
126
|
outputTokens: message.usage?.output_tokens ?? 0
|
|
95
|
-
});
|
|
127
|
+
}));
|
|
96
128
|
if (message.stop_reason !== "max_tokens") {
|
|
97
129
|
return { text: fullText, usage };
|
|
98
130
|
}
|
|
@@ -104,19 +136,19 @@ async function createCompleteMessage(claude, params, { maxContinuations = DEFAUL
|
|
|
104
136
|
var MODEL_REGISTRY = {
|
|
105
137
|
fast: {
|
|
106
138
|
description: "Quick, cheap work where depth matters least.",
|
|
107
|
-
models: [{ id: "claude-haiku-4-5-20251001", label: "Haiku 4.5" }]
|
|
139
|
+
models: [{ id: "claude-haiku-4-5-20251001", label: "Haiku 4.5", contextTokens: 2e5 }]
|
|
108
140
|
},
|
|
109
141
|
balanced: {
|
|
110
142
|
description: "The everyday tier: chat, log entries, release notes, the daily digest. Frequent enough that cost matters.",
|
|
111
|
-
models: [{ id: "claude-sonnet-5", label: "Sonnet 5" }]
|
|
143
|
+
models: [{ id: "claude-sonnet-5", label: "Sonnet 5", contextTokens: 2e5 }]
|
|
112
144
|
},
|
|
113
145
|
capable: {
|
|
114
146
|
description: "Deeper reasoning for infrequent, high-value work where getting it right outweighs the cost.",
|
|
115
|
-
models: [{ id: "claude-opus-5", label: "Opus 5" }]
|
|
147
|
+
models: [{ id: "claude-opus-5", label: "Opus 5", contextTokens: 2e5 }]
|
|
116
148
|
},
|
|
117
149
|
max: {
|
|
118
150
|
description: "Maximum depth, for long-running agentic work. Opt-in \u2014 the most expensive tier.",
|
|
119
|
-
models: [{ id: "claude-fable-5", label: "Fable 5" }]
|
|
151
|
+
models: [{ id: "claude-fable-5", label: "Fable 5", contextTokens: 2e5 }]
|
|
120
152
|
}
|
|
121
153
|
};
|
|
122
154
|
function defaultModelFor(tier) {
|
|
@@ -280,66 +312,7 @@ async function runCiEntrypoint(source, main2, deps = defaultDeps) {
|
|
|
280
312
|
}
|
|
281
313
|
}
|
|
282
314
|
|
|
283
|
-
// ../../modules/
|
|
284
|
-
function decisionEntriesDir(projectName) {
|
|
285
|
-
return `projects/${projectName}/decisions`;
|
|
286
|
-
}
|
|
287
|
-
async function listDecisionEntries(octokit, { owner, repo, projectName }) {
|
|
288
|
-
const dir = decisionEntriesDir(projectName);
|
|
289
|
-
let files;
|
|
290
|
-
try {
|
|
291
|
-
const { data } = await octokit.rest.repos.getContent({ owner, repo, path: dir });
|
|
292
|
-
if (!Array.isArray(data)) {
|
|
293
|
-
return [];
|
|
294
|
-
}
|
|
295
|
-
files = data;
|
|
296
|
-
} catch (error) {
|
|
297
|
-
if (isNotFound(error)) {
|
|
298
|
-
return [];
|
|
299
|
-
}
|
|
300
|
-
throw error;
|
|
301
|
-
}
|
|
302
|
-
const entries = await Promise.all(files.filter((file) => file.type === "file").map(async (file) => {
|
|
303
|
-
const { data } = await octokit.rest.repos.getContent({ owner, repo, path: file.path });
|
|
304
|
-
if (Array.isArray(data) || data.type !== "file") {
|
|
305
|
-
return null;
|
|
306
|
-
}
|
|
307
|
-
return { path: file.path, content: Buffer.from(data.content, "base64").toString("utf-8") };
|
|
308
|
-
}));
|
|
309
|
-
return entries.filter((entry) => entry !== null).sort((a, b) => a.path.localeCompare(b.path));
|
|
310
|
-
}
|
|
311
|
-
function isNotFound(error) {
|
|
312
|
-
return typeof error === "object" && error !== null && "status" in error && error.status === 404;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// ../../modules/devlore/dist/prompt.js
|
|
316
|
-
function buildOnboardingGuidePrompt(input) {
|
|
317
|
-
return [
|
|
318
|
-
`Project: ${input.projectName}`,
|
|
319
|
-
"",
|
|
320
|
-
'Write a "start here" guide for a developer joining this project. Not an end-user manual, and not a changelog \u2014 an orientation for someone about to read and change the code.',
|
|
321
|
-
"",
|
|
322
|
-
input.currentState ? `--- Current state ---
|
|
323
|
-
${input.currentState}` : "",
|
|
324
|
-
input.canonicalDocs ? `--- Canonical docs ---
|
|
325
|
-
${input.canonicalDocs}` : "",
|
|
326
|
-
input.decisions.length > 0 ? `--- Decisions ---
|
|
327
|
-
${input.decisions.join("\n\n")}` : "",
|
|
328
|
-
"",
|
|
329
|
-
"Cover, in whatever order serves a newcomer best:",
|
|
330
|
-
"",
|
|
331
|
-
"- Where the important logic lives, and what to read first.",
|
|
332
|
-
"- Why the significant past decisions were made \u2014 a newcomer who does not know the reasoning will undo it.",
|
|
333
|
-
"- Common gotchas: the things that look wrong but are deliberate, and the mistakes this codebase has actually made before.",
|
|
334
|
-
"- How to get from a clean checkout to a working change.",
|
|
335
|
-
"",
|
|
336
|
-
"Draw only on what the documents above actually say. Where they do not cover something a newcomer would need, say so plainly rather than inventing a plausible answer \u2014 this guide is read by someone with no way to tell the difference.",
|
|
337
|
-
"",
|
|
338
|
-
"Return the guide only \u2014 no preamble, no meta-commentary."
|
|
339
|
-
].filter((line) => line !== "").join("\n");
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// ../../modules/devlore/dist/ciUsage.js
|
|
315
|
+
// ../../modules/core/dist/ciUsage.js
|
|
343
316
|
var EMPTY_PROJECT_USAGE = {
|
|
344
317
|
draftLogEntry: ZERO_USAGE,
|
|
345
318
|
consolidateRelease: ZERO_USAGE,
|
|
@@ -350,6 +323,7 @@ var EMPTY_PROJECT_USAGE = {
|
|
|
350
323
|
syncOnboarding: ZERO_USAGE,
|
|
351
324
|
releaseNotes: ZERO_USAGE,
|
|
352
325
|
captureBaseline: ZERO_USAGE,
|
|
326
|
+
devseerReview: ZERO_USAGE,
|
|
353
327
|
rollover: ZERO_ROLLOVER_USAGE
|
|
354
328
|
};
|
|
355
329
|
function usagePath(projectName) {
|
|
@@ -369,21 +343,31 @@ async function getUsageFile(octokit, { owner, repo, projectName }) {
|
|
|
369
343
|
const usage = { ...EMPTY_PROJECT_USAGE, ...parsed };
|
|
370
344
|
return { usage, sha: data.sha };
|
|
371
345
|
} catch (error) {
|
|
372
|
-
if (
|
|
346
|
+
if (isNotFound(error)) {
|
|
373
347
|
return { usage: EMPTY_PROJECT_USAGE };
|
|
374
348
|
}
|
|
375
349
|
throw error;
|
|
376
350
|
}
|
|
377
351
|
}
|
|
378
352
|
function addUsage(current, source, delta, now = /* @__PURE__ */ new Date()) {
|
|
353
|
+
const bySource = current.rolloverBySource ?? {};
|
|
379
354
|
return {
|
|
380
355
|
...current,
|
|
381
356
|
[source]: addTokenUsage(current[source], delta),
|
|
382
|
-
|
|
357
|
+
// Both: the combined bucket stays the total it always was, and the
|
|
358
|
+
// per-source split is what lets the dashboard attribute it.
|
|
359
|
+
rollover: addRolloverUsage(current.rollover, delta, now),
|
|
360
|
+
rolloverBySource: {
|
|
361
|
+
...bySource,
|
|
362
|
+
[source]: addRolloverUsage(bySource[source] ?? ZERO_ROLLOVER_USAGE, delta, now)
|
|
363
|
+
}
|
|
383
364
|
};
|
|
384
365
|
}
|
|
385
366
|
async function recordUsage(octokit, params, source, delta, now = /* @__PURE__ */ new Date()) {
|
|
386
367
|
const { owner, repo, projectName, branch } = params;
|
|
368
|
+
if (delta.inputTokens === 0 && delta.outputTokens === 0) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
387
371
|
await retryOnConflict(async () => {
|
|
388
372
|
const { usage: current, sha } = await getUsageFile(octokit, { owner, repo, projectName });
|
|
389
373
|
const updated = addUsage(current, source, delta, now);
|
|
@@ -398,10 +382,78 @@ async function recordUsage(octokit, params, source, delta, now = /* @__PURE__ */
|
|
|
398
382
|
});
|
|
399
383
|
});
|
|
400
384
|
}
|
|
385
|
+
function isNotFound(error) {
|
|
386
|
+
return typeof error === "object" && error !== null && "status" in error && error.status === 404;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ../../modules/devlore/dist/decisionEntries.js
|
|
390
|
+
function decisionEntriesDir(projectName) {
|
|
391
|
+
return `projects/${projectName}/decisions`;
|
|
392
|
+
}
|
|
393
|
+
async function listDecisionEntries(octokit, { owner, repo, projectName }) {
|
|
394
|
+
const dir = decisionEntriesDir(projectName);
|
|
395
|
+
let files;
|
|
396
|
+
try {
|
|
397
|
+
const { data } = await octokit.rest.repos.getContent({ owner, repo, path: dir });
|
|
398
|
+
if (!Array.isArray(data)) {
|
|
399
|
+
return [];
|
|
400
|
+
}
|
|
401
|
+
files = data;
|
|
402
|
+
} catch (error) {
|
|
403
|
+
if (isNotFound2(error)) {
|
|
404
|
+
return [];
|
|
405
|
+
}
|
|
406
|
+
throw error;
|
|
407
|
+
}
|
|
408
|
+
const entries = await Promise.all(files.filter((file) => file.type === "file").map(async (file) => {
|
|
409
|
+
const { data } = await octokit.rest.repos.getContent({ owner, repo, path: file.path });
|
|
410
|
+
if (Array.isArray(data) || data.type !== "file") {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
return { path: file.path, content: Buffer.from(data.content, "base64").toString("utf-8") };
|
|
414
|
+
}));
|
|
415
|
+
return entries.filter((entry) => entry !== null).sort((a, b) => a.path.localeCompare(b.path));
|
|
416
|
+
}
|
|
401
417
|
function isNotFound2(error) {
|
|
402
418
|
return typeof error === "object" && error !== null && "status" in error && error.status === 404;
|
|
403
419
|
}
|
|
404
420
|
|
|
421
|
+
// ../../modules/devlore/dist/prompt.js
|
|
422
|
+
var SUPERSEDED_GUIDANCE = [
|
|
423
|
+
"These documents record how the thinking changed over time, so they contain claims that were later corrected, and the corrected version is still physically present.",
|
|
424
|
+
"**A correction outranks what it corrects.** Where two passages disagree, the later-dated one is what is true now. A passage struck through, marked superseded, described as decided against, or sitting under a heading that says the earlier view was wrong, records what was believed \u2014 never what is. Do not restate any of it as current."
|
|
425
|
+
].join("\n");
|
|
426
|
+
var SUPERSEDED_GUIDANCE_WITH_LESSONS = [
|
|
427
|
+
SUPERSEDED_GUIDANCE,
|
|
428
|
+
"A reversed decision is still worth telling a newcomer about \u2014 but as something the project learned, never as a rule they should follow. Say what is true now first, and mention the earlier version only where the fact that it changed is itself the lesson."
|
|
429
|
+
].join("\n");
|
|
430
|
+
function buildOnboardingGuidePrompt(input) {
|
|
431
|
+
return [
|
|
432
|
+
`Project: ${input.projectName}`,
|
|
433
|
+
"",
|
|
434
|
+
'Write a "start here" guide for a developer joining this project. Not an end-user manual, and not a changelog \u2014 an orientation for someone about to read and change the code.',
|
|
435
|
+
"",
|
|
436
|
+
input.currentState ? `--- Current state ---
|
|
437
|
+
${input.currentState}` : "",
|
|
438
|
+
input.canonicalDocs ? `--- Canonical docs ---
|
|
439
|
+
${input.canonicalDocs}` : "",
|
|
440
|
+
input.decisions.length > 0 ? `--- Decisions ---
|
|
441
|
+
${input.decisions.join("\n\n")}` : "",
|
|
442
|
+
"",
|
|
443
|
+
"Cover, in whatever order serves a newcomer best:",
|
|
444
|
+
"",
|
|
445
|
+
"- Where the important logic lives, and what to read first.",
|
|
446
|
+
"- Why the significant past decisions were made \u2014 a newcomer who does not know the reasoning will undo it.",
|
|
447
|
+
"- Common gotchas: the things that look wrong but are deliberate, and the mistakes this codebase has actually made before.",
|
|
448
|
+
"- How to get from a clean checkout to a working change.",
|
|
449
|
+
"",
|
|
450
|
+
"Draw only on what the documents above actually say. Where they do not cover something a newcomer would need, say so plainly rather than inventing a plausible answer \u2014 this guide is read by someone with no way to tell the difference.",
|
|
451
|
+
SUPERSEDED_GUIDANCE_WITH_LESSONS,
|
|
452
|
+
"",
|
|
453
|
+
"Return the guide only \u2014 no preamble, no meta-commentary."
|
|
454
|
+
].filter((line) => line !== "").join("\n");
|
|
455
|
+
}
|
|
456
|
+
|
|
405
457
|
// ../../modules/devlore/dist/canonicalDocs.js
|
|
406
458
|
function canonicalDocsPath(projectName) {
|
|
407
459
|
return `projects/${projectName}/canonical/README.md`;
|
|
@@ -511,11 +563,14 @@ async function syncOnboarding({ projectOctokit, vaultOctokit, claude, projectOwn
|
|
|
511
563
|
${text.trim()}
|
|
512
564
|
`;
|
|
513
565
|
const existing = await getExistingOnboarding(projectOctokit, { owner: projectOwner, repo: projectRepoName });
|
|
566
|
+
const releaseTag = releaseTagFromEnvironment();
|
|
567
|
+
const wrote = existing ? "Update" : "Add";
|
|
568
|
+
const message = releaseTag ? `${wrote} onboarding guide (release ${releaseTag})` : `${wrote} onboarding guide`;
|
|
514
569
|
await projectOctokit.rest.repos.createOrUpdateFileContents({
|
|
515
570
|
owner: projectOwner,
|
|
516
571
|
repo: projectRepoName,
|
|
517
572
|
path: ONBOARDING_PATH,
|
|
518
|
-
message
|
|
573
|
+
message,
|
|
519
574
|
content: Buffer.from(content, "utf-8").toString("base64"),
|
|
520
575
|
sha: existing?.sha
|
|
521
576
|
});
|
|
@@ -531,7 +586,7 @@ ${text.trim()}
|
|
|
531
586
|
owner: vaultOwner,
|
|
532
587
|
repo: vaultRepoName,
|
|
533
588
|
path: vaultPath,
|
|
534
|
-
message: `Mirror onboarding guide: ${projectName}`,
|
|
589
|
+
message: releaseTag ? `Mirror onboarding guide: ${projectName} (release ${releaseTag})` : `Mirror onboarding guide: ${projectName}`,
|
|
535
590
|
content: Buffer.from(mirrored, "utf-8").toString("base64"),
|
|
536
591
|
branch: VAULT_BRANCH2,
|
|
537
592
|
sha: existingVault.sha
|
|
@@ -542,6 +597,12 @@ ${text.trim()}
|
|
|
542
597
|
}
|
|
543
598
|
return { path: ONBOARDING_PATH, vaultPath, content, usage };
|
|
544
599
|
}
|
|
600
|
+
function releaseTagFromEnvironment() {
|
|
601
|
+
if (process.env.GITHUB_REF_TYPE !== "tag")
|
|
602
|
+
return null;
|
|
603
|
+
const name = process.env.GITHUB_REF_NAME?.trim();
|
|
604
|
+
return name ? name : null;
|
|
605
|
+
}
|
|
545
606
|
|
|
546
607
|
// ../../modules/devlore/dist/bin/syncOnboardingCli.js
|
|
547
608
|
function requireEnv(env, name) {
|