@rhei-team/rhei 1.0.0-beta.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/README.md +1048 -0
- package/bin/rhei-mcp.js +3 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +86366 -0
- package/dist/premium/contracts.d.ts +445 -0
- package/dist/premium/contracts.js +97 -0
- package/dist/vendor/rhei-core/briefs.js +1276 -0
- package/dist/vendor/rhei-core/codeAgent.js +615 -0
- package/dist/vendor/rhei-core/codeEditSession.js +293 -0
- package/dist/vendor/rhei-core/codeIntelligence.js +4287 -0
- package/dist/vendor/rhei-core/codeMarket.js +8946 -0
- package/dist/vendor/rhei-core/codeReviewIntelligence.js +5918 -0
- package/dist/vendor/rhei-core/codeSemantics.js +172427 -0
- package/dist/vendor/rhei-core/codeStory.js +667 -0
- package/dist/vendor/rhei-core/codeStrategyPlan.js +663 -0
- package/dist/vendor/rhei-core/codeTrail.js +2781 -0
- package/dist/vendor/rhei-core/codeWorkHandoff.js +281 -0
- package/dist/vendor/rhei-core/contextQuery.js +1119 -0
- package/dist/vendor/rhei-core/contextRouting.js +2052 -0
- package/dist/vendor/rhei-core/evidenceLedger.js +5336 -0
- package/dist/vendor/rhei-core/executionSafety.js +0 -0
- package/dist/vendor/rhei-core/goalIntelligence.js +2218 -0
- package/dist/vendor/rhei-core/model-lanes.js +75 -0
- package/dist/vendor/rhei-core/now.js +127 -0
- package/dist/vendor/rhei-core/package.json +29 -0
- package/dist/vendor/rhei-core/programPlan.js +3153 -0
- package/dist/vendor/rhei-core/search.js +196 -0
- package/dist/vendor/rhei-core/serviceIntelligence.js +1734 -0
- package/dist/vendor/rhei-core/workflowPlan.js +1660 -0
- package/package.json +41 -0
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
// ../core/src/codeStory/types.ts
|
|
2
|
+
var CODE_CHANGE_STORY_SCHEMA_VERSION = 1;
|
|
3
|
+
// ../core/src/codeStory/builders.ts
|
|
4
|
+
var NO_AUTHORITY = {
|
|
5
|
+
reportOnly: true,
|
|
6
|
+
advisoryOnly: true,
|
|
7
|
+
noAuthority: true,
|
|
8
|
+
grantsApplyAuthority: false,
|
|
9
|
+
grantsMergeAuthority: false,
|
|
10
|
+
grantsGitHubWriteAuthority: false,
|
|
11
|
+
grantsMemoryPromotionAuthority: false,
|
|
12
|
+
grantsServiceWriteAuthority: false,
|
|
13
|
+
grantsGraphMutationAuthority: false
|
|
14
|
+
};
|
|
15
|
+
function codeChangeStoryNoAuthorityBoundaryV1() {
|
|
16
|
+
return { ...NO_AUTHORITY };
|
|
17
|
+
}
|
|
18
|
+
function buildCodeChangeStoryFromPrReviewV1(args) {
|
|
19
|
+
const generatedAt = args.generatedAt ?? args.prReviewStory.generatedAt;
|
|
20
|
+
const story = args.prReviewStory;
|
|
21
|
+
const headline = buildStoryHeadline(story);
|
|
22
|
+
const summary = story.topMotion.detail ?? `${story.counts.changedFiles} changed files, ${story.counts.blockers} blockers, ${story.counts.proofGaps} proof gaps.`;
|
|
23
|
+
const sourceRefs = buildSourceRefs(story);
|
|
24
|
+
const contextRefs = buildContextRefs(story);
|
|
25
|
+
const chapters = buildStoryChapters(story, args.reviewRoomState, args.reviewDiff);
|
|
26
|
+
const nextActions = buildStoryNextActions(story);
|
|
27
|
+
return {
|
|
28
|
+
schemaVersion: CODE_CHANGE_STORY_SCHEMA_VERSION,
|
|
29
|
+
kind: "code_change_story",
|
|
30
|
+
storyId: stableId("code-change-story", [story.storyId, story.pr.prSnapshotId, story.reviewDiffId, story.reviewRoomId]),
|
|
31
|
+
source: {
|
|
32
|
+
sourceKind: "pr_review",
|
|
33
|
+
sourceStoryId: story.storyId,
|
|
34
|
+
prSnapshotId: story.pr.prSnapshotId,
|
|
35
|
+
reviewDiffId: story.reviewDiffId,
|
|
36
|
+
reviewRoomId: story.reviewRoomId,
|
|
37
|
+
generatedFrom: "PrReviewStoryV1",
|
|
38
|
+
refs: sourceRefs
|
|
39
|
+
},
|
|
40
|
+
status: story.status,
|
|
41
|
+
headline,
|
|
42
|
+
summary,
|
|
43
|
+
chapters,
|
|
44
|
+
contextRefs,
|
|
45
|
+
sourceReceipts: story.sourceReceipts,
|
|
46
|
+
nextActions,
|
|
47
|
+
generationMode: "deterministic",
|
|
48
|
+
proofStatus: proofStatusForStory(story),
|
|
49
|
+
freshness: story.freshness,
|
|
50
|
+
relatedPrs: story.relatedPrs,
|
|
51
|
+
generatedAt,
|
|
52
|
+
...codeChangeStoryNoAuthorityBoundaryV1()
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function buildCodeChangeStoryFromCodeTrailV1(args) {
|
|
56
|
+
const trail = args.codeTrail;
|
|
57
|
+
const generatedAt = args.generatedAt ?? trail.generatedAt;
|
|
58
|
+
const repoId = stringValue(args.repoId) ?? stringValue(trail.scope.repoId);
|
|
59
|
+
if (trail.entries.length === 0)
|
|
60
|
+
return codeTrailStoryGap("empty_trail", "CodeTrail has no entries to summarize.");
|
|
61
|
+
if (!repoId)
|
|
62
|
+
return codeTrailStoryGap("missing_repo", "CodeTrail story creation requires a repository scope.");
|
|
63
|
+
if (isPrScopedCodeTrail(trail)) {
|
|
64
|
+
return codeTrailStoryGap("pr_scoped_trail", "PR-scoped CodeTrail evidence is left to the PR Review story producer.");
|
|
65
|
+
}
|
|
66
|
+
const contextRefs = buildCodeTrailContextRefs(trail, repoId);
|
|
67
|
+
const fileRefsForStory = contextRefs.filter((ref) => ref.kind === "file" || Boolean(ref.path));
|
|
68
|
+
if (fileRefsForStory.length === 0) {
|
|
69
|
+
return codeTrailStoryGap("no_code_refs", "CodeTrail story creation requires at least one code/file reference.");
|
|
70
|
+
}
|
|
71
|
+
const sourceIdentity = args.sourceIdentity ?? {};
|
|
72
|
+
const sourceStoryId = codeTrailSourceStoryId(trail, sourceIdentity);
|
|
73
|
+
const sourceRefs = buildCodeTrailSourceRefs(trail, sourceIdentity);
|
|
74
|
+
const status = codeTrailStoryStatus(trail);
|
|
75
|
+
const headline = codeTrailHeadline(trail, fileRefsForStory.length);
|
|
76
|
+
const summary = codeTrailSummary(trail, fileRefsForStory.length);
|
|
77
|
+
const storySeed = stableId("code-trail-story-source", [trail.projectId, repoId, sourceStoryId]);
|
|
78
|
+
return {
|
|
79
|
+
story: {
|
|
80
|
+
schemaVersion: CODE_CHANGE_STORY_SCHEMA_VERSION,
|
|
81
|
+
kind: "code_change_story",
|
|
82
|
+
storyId: stableId("code-change-story", ["code_trail", trail.projectId, repoId, sourceStoryId]),
|
|
83
|
+
source: {
|
|
84
|
+
sourceKind: "code_trail",
|
|
85
|
+
kind: "code_trail",
|
|
86
|
+
sourceStoryId,
|
|
87
|
+
generatedFrom: "CodeTrail",
|
|
88
|
+
refs: sourceRefs
|
|
89
|
+
},
|
|
90
|
+
status,
|
|
91
|
+
headline,
|
|
92
|
+
summary,
|
|
93
|
+
chapters: buildCodeTrailChapters(trail, fileRefsForStory, storySeed),
|
|
94
|
+
contextRefs,
|
|
95
|
+
sourceReceipts: buildCodeTrailSourceReceipts(trail, generatedAt),
|
|
96
|
+
nextActions: [{
|
|
97
|
+
actionId: stableId("code-story-action", [storySeed, "collect_evidence"]),
|
|
98
|
+
kind: "collect_evidence",
|
|
99
|
+
title: "Review CodeTrail evidence",
|
|
100
|
+
detail: "Inspect the imported Trail and attach validation or review evidence before taking any governed action.",
|
|
101
|
+
filePaths: fileRefsForStory.map((ref) => ref.path ?? ref.id).slice(0, 12),
|
|
102
|
+
refs: sourceRefs.map((ref) => `${ref.kind}:${ref.id}`).slice(0, 12),
|
|
103
|
+
reasonCodes: ["next_action:collect_evidence", "authority:report_only_no_action"]
|
|
104
|
+
}],
|
|
105
|
+
generationMode: "deterministic",
|
|
106
|
+
proofStatus: codeTrailProofStatus(trail, fileRefsForStory.length),
|
|
107
|
+
freshness: {
|
|
108
|
+
status: "fresh",
|
|
109
|
+
generatedAt,
|
|
110
|
+
snapshotUpdatedAt: latestTrailUpdatedAt(trail) ?? generatedAt,
|
|
111
|
+
reasons: ["source:code_trail", "freshness:import_projection"]
|
|
112
|
+
},
|
|
113
|
+
relatedPrs: [],
|
|
114
|
+
generatedAt,
|
|
115
|
+
...codeChangeStoryNoAuthorityBoundaryV1()
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function buildReviewContinuityV1(args = {}) {
|
|
120
|
+
const story = args.prReviewStory ?? undefined;
|
|
121
|
+
const reviewRoomState = recordValue(args.reviewRoomState);
|
|
122
|
+
const generatedAt = args.generatedAt ?? story?.generatedAt ?? numberValue(reviewRoomState["generatedAt"], Date.now());
|
|
123
|
+
const signals = recordValue(reviewRoomState["signals"]);
|
|
124
|
+
const checkTriage = recordValue(reviewRoomState["checkTriage"]);
|
|
125
|
+
const staleApproval = recordValue(reviewRoomState["staleApproval"]);
|
|
126
|
+
const reviewOrder = arrayValue(reviewRoomState["reviewOrder"]);
|
|
127
|
+
const blockers = arrayValue(reviewRoomState["blockers"]);
|
|
128
|
+
const proofGaps = arrayValue(reviewRoomState["proofGaps"]);
|
|
129
|
+
const failedChecks = arrayValue(checkTriage["failedChecks"]);
|
|
130
|
+
const counts = {
|
|
131
|
+
blockerCount: story?.counts.blockers ?? numberValue(signals["blockerCount"], blockers.length),
|
|
132
|
+
proofGapCount: story?.counts.proofGaps ?? numberValue(signals["proofGapCount"], proofGaps.length),
|
|
133
|
+
failedCheckCount: story?.counts.failedChecks ?? numberValue(signals["failedCheckCount"], failedChecks.length),
|
|
134
|
+
staleApprovalCount: story?.counts.staleApprovals ?? numberValue(signals["staleApprovalCount"], staleApproval["state"] === "stale" ? 1 : 0),
|
|
135
|
+
currentApprovalCount: story?.counts.currentApprovals ?? numberValue(signals["currentApprovalCount"], numberValue(staleApproval["currentApprovalCount"], 0)),
|
|
136
|
+
reviewOrderCount: reviewOrder.length || story?.keyFiles.length || 0
|
|
137
|
+
};
|
|
138
|
+
const status = reviewContinuityStatus({ story, staleApproval, counts });
|
|
139
|
+
const nextAction = continuityNextAction({ story, status, reviewRoomState, blockers, proofGaps, failedChecks });
|
|
140
|
+
const sourceReceipts = args.sourceReceipts ?? story?.sourceReceipts ?? [];
|
|
141
|
+
const sourceReceiptRefs = sourceReceipts.map((receipt) => receipt.source).filter(Boolean).map(String);
|
|
142
|
+
const precedentRefs = story?.relatedPrs.map((related) => related.prSnapshotId ?? related.title).filter(isString) ?? [];
|
|
143
|
+
const reviewDiffId = story?.reviewDiffId ?? stringValue(recordValue(args.reviewDiff)["reviewDiffId"]);
|
|
144
|
+
const reviewRoomId = story?.reviewRoomId ?? stringValue(recordValue(recordValue(args.reviewRoomState)["source"])["reviewRoomId"]);
|
|
145
|
+
const reasonCodes = uniqueStrings([
|
|
146
|
+
`continuity:${status}`,
|
|
147
|
+
counts.blockerCount > 0 ? "blocked:blockers_present" : undefined,
|
|
148
|
+
counts.failedCheckCount > 0 ? "blocked:failed_checks_present" : undefined,
|
|
149
|
+
counts.staleApprovalCount > 0 ? "freshness:approval_stale" : undefined,
|
|
150
|
+
story?.freshness.status === "stale" ? "freshness:story_stale" : undefined,
|
|
151
|
+
counts.proofGapCount > 0 ? "proof:gaps_present" : undefined,
|
|
152
|
+
counts.currentApprovalCount > 0 ? "review:current_approval_present" : undefined,
|
|
153
|
+
args.importedCodeTrailRefs?.length ? "trail:imported_refs_present" : undefined
|
|
154
|
+
]);
|
|
155
|
+
return {
|
|
156
|
+
schemaVersion: CODE_CHANGE_STORY_SCHEMA_VERSION,
|
|
157
|
+
kind: "pr_review_continuity",
|
|
158
|
+
continuityId: stableId("pr-review-continuity", [story?.storyId, reviewDiffId, reviewRoomId, status]),
|
|
159
|
+
status,
|
|
160
|
+
nextAction,
|
|
161
|
+
source: {
|
|
162
|
+
prReviewStoryId: story?.storyId,
|
|
163
|
+
reviewDiffId,
|
|
164
|
+
reviewRoomId,
|
|
165
|
+
importedCodeTrailRefs: uniqueStrings(args.importedCodeTrailRefs ?? []),
|
|
166
|
+
precedentRefs: uniqueStrings(precedentRefs),
|
|
167
|
+
sourceReceiptRefs: uniqueStrings(sourceReceiptRefs)
|
|
168
|
+
},
|
|
169
|
+
signals: counts,
|
|
170
|
+
reasonCodes,
|
|
171
|
+
generatedAt,
|
|
172
|
+
...codeChangeStoryNoAuthorityBoundaryV1()
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function codeTrailStoryGap(reason, detail) {
|
|
176
|
+
return {
|
|
177
|
+
skippedReason: reason,
|
|
178
|
+
gap: {
|
|
179
|
+
kind: "code_trail_story_not_persisted",
|
|
180
|
+
reason,
|
|
181
|
+
detail,
|
|
182
|
+
reportOnly: true,
|
|
183
|
+
noAuthority: true
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function isPrScopedCodeTrail(trail) {
|
|
188
|
+
if (stringValue(trail.scope.prSnapshotId) || stringValue(trail.scope.reviewRoomId) || stringValue(trail.scope.storyId))
|
|
189
|
+
return true;
|
|
190
|
+
if (trail.scope.surface === "pr_review")
|
|
191
|
+
return true;
|
|
192
|
+
return trail.entries.some((entry) => {
|
|
193
|
+
if (entry.entryKind === "pr_snapshot" || entry.entryKind === "pr_review_file")
|
|
194
|
+
return true;
|
|
195
|
+
return refsForCodeTrailEntry(entry).some((ref) => ref.kind === "pr_snapshot" || ref.kind === "review_room" || ref.kind === "code_change_story");
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
function buildCodeTrailContextRefs(trail, repoId) {
|
|
199
|
+
return uniqueRefs(trail.entries.flatMap((entry) => refsForCodeTrailEntry(entry).map((ref) => codeTrailContextRef(ref, entry, repoId))).filter(Boolean));
|
|
200
|
+
}
|
|
201
|
+
function buildCodeTrailSourceRefs(trail, identity) {
|
|
202
|
+
const identityRefs = [
|
|
203
|
+
identity.importId ? storyRef("trail_import", identity.importId, "Trail import", ["source:trail_import"]) : undefined,
|
|
204
|
+
identity.trailHash ? storyRef("trail_hash", identity.trailHash, "Trail hash", ["source:trail_hash"]) : undefined,
|
|
205
|
+
identity.sourceMcpRequestId ? storyRef("mcp_context_run", identity.sourceMcpRequestId, "MCP context request", ["source:mcp_context_request"]) : undefined,
|
|
206
|
+
identity.sourceMcpRunId ? storyRef("mcp_run_graph", identity.sourceMcpRunId, "MCP run", ["source:mcp_run"]) : undefined,
|
|
207
|
+
identity.sourceRunId ? storyRef("source_run", identity.sourceRunId, "Source run", ["source:run"]) : undefined,
|
|
208
|
+
identity.sessionId ? storyRef("session", identity.sessionId, "Code session", ["source:session"]) : undefined,
|
|
209
|
+
identity.workroomRunId ? storyRef("code_workroom_run", identity.workroomRunId, "Workroom run", ["source:workroom_run"]) : undefined,
|
|
210
|
+
identity.patchRunId ? storyRef("code_patch_run", identity.patchRunId, "Patch run", ["source:patch_run"]) : undefined,
|
|
211
|
+
identity.editSessionRowId ? storyRef("agent_edit_session", identity.editSessionRowId, "Edit session", ["source:edit_session_row"]) : undefined,
|
|
212
|
+
identity.editSessionId ? storyRef("agent_edit_session", identity.editSessionId, "Edit session", ["source:edit_session"]) : undefined,
|
|
213
|
+
identity.truthCandidateId ? storyRef("truth_candidate", identity.truthCandidateId, "Truth candidate", ["source:truth_candidate"]) : undefined,
|
|
214
|
+
identity.capabilityRunId ? storyRef("capability_run", identity.capabilityRunId, "Capability run", ["source:capability_run"]) : undefined
|
|
215
|
+
];
|
|
216
|
+
const sourceRefs = trail.sources.slice(0, 16).map((source) => {
|
|
217
|
+
const ref = source.ref;
|
|
218
|
+
const id = ref?.id ?? source.watermark ?? source.key;
|
|
219
|
+
if (!id)
|
|
220
|
+
return;
|
|
221
|
+
return storyRef(ref?.kind ?? source.kind ?? "code_trail_source", id, source.label ?? ref?.label ?? source.kind ?? "CodeTrail source", ["source:code_trail", source.kind ? `trail_source:${source.kind}` : undefined].filter(isString), ref?.path, ref?.href);
|
|
222
|
+
});
|
|
223
|
+
return uniqueRefs([...identityRefs, ...sourceRefs]);
|
|
224
|
+
}
|
|
225
|
+
function codeTrailContextRef(ref, entry, repoId) {
|
|
226
|
+
const filePath = codeTrailFilePath(ref);
|
|
227
|
+
if (filePath) {
|
|
228
|
+
return storyRef("file", filePath, ref.label ?? filePath, uniqueStrings(["context:code_trail", `entry:${entry.entryKind}`, ...entry.reasonCodes]), filePath, ref.href, entry.status);
|
|
229
|
+
}
|
|
230
|
+
if (["code_node", "artifact", "context_receipt", "code_patch_run", "agent_edit_session", "truth_candidate", "evidence_job", "capability_run", "mcp_context_run", "mcp_run_graph", "code_write_proposal"].includes(ref.kind)) {
|
|
231
|
+
return storyRef(ref.kind, ref.id, ref.label ?? ref.kind, uniqueStrings(["context:code_trail", `entry:${entry.entryKind}`, repoId ? `repo:${repoId}` : undefined]), ref.path, ref.href, entry.status);
|
|
232
|
+
}
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
function refsForCodeTrailEntry(entry) {
|
|
236
|
+
return [
|
|
237
|
+
entry.primaryRef,
|
|
238
|
+
...entry.sourceRefs,
|
|
239
|
+
...entry.inputRefs,
|
|
240
|
+
...entry.outputRefs,
|
|
241
|
+
...entry.evidenceRefs,
|
|
242
|
+
...entry.receiptRefs,
|
|
243
|
+
...entry.relatedRefs
|
|
244
|
+
].filter(Boolean);
|
|
245
|
+
}
|
|
246
|
+
function codeTrailFilePath(ref) {
|
|
247
|
+
const path = stringValue(ref.path);
|
|
248
|
+
if (path)
|
|
249
|
+
return path;
|
|
250
|
+
if (ref.kind === "file")
|
|
251
|
+
return stringValue(ref.id);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
function codeTrailSourceStoryId(trail, identity) {
|
|
255
|
+
return stringValue(identity.trailHash) ?? stringValue(identity.importId) ?? stringValue(identity.sourceMcpRunId) ?? stringValue(identity.sourceRunId) ?? stringValue(identity.sourceMcpRequestId) ?? stableId("code-trail-source", [trail.projectId, trail.scope.repoId, trail.generatedAt.toString(), trail.summary.title]);
|
|
256
|
+
}
|
|
257
|
+
function codeTrailHeadline(trail, fileCount) {
|
|
258
|
+
const title = stringValue(trail.summary.title);
|
|
259
|
+
if (title && title !== "Code Trail")
|
|
260
|
+
return title;
|
|
261
|
+
return `CodeTrail story for ${fileCount} code ${fileCount === 1 ? "ref" : "refs"}`;
|
|
262
|
+
}
|
|
263
|
+
function codeTrailSummary(trail, fileCount) {
|
|
264
|
+
const detail = stringValue(trail.summary.detail);
|
|
265
|
+
if (detail)
|
|
266
|
+
return detail;
|
|
267
|
+
return `${trail.entries.length} Trail entries and ${fileCount} code refs from ${trail.sources.length} sources.`;
|
|
268
|
+
}
|
|
269
|
+
function buildCodeTrailChapters(trail, fileRefsForStory, storySeed) {
|
|
270
|
+
const fileSections = fileRefsForStory.slice(0, 12).map((ref) => ({
|
|
271
|
+
sectionId: stableId("code-story-section", [storySeed, "file", ref.id]),
|
|
272
|
+
title: ref.path ?? ref.label ?? ref.id,
|
|
273
|
+
summary: ref.status ? `Observed in CodeTrail with status ${ref.status}.` : "Observed in CodeTrail evidence.",
|
|
274
|
+
filePaths: ref.path ? [ref.path] : [ref.id],
|
|
275
|
+
refs: [`${ref.kind}:${ref.id}`],
|
|
276
|
+
reasonCodes: uniqueStrings(["section:code_trail_file", ...ref.reasonCodes ?? []])
|
|
277
|
+
}));
|
|
278
|
+
const evidenceSections = trail.entries.slice(0, 8).map((entry) => ({
|
|
279
|
+
sectionId: stableId("code-story-section", [storySeed, "entry", entry.stableKey]),
|
|
280
|
+
title: entry.title ?? entry.entryKind,
|
|
281
|
+
summary: entry.summary ?? `${entry.phase} · ${entry.status}`,
|
|
282
|
+
filePaths: uniqueStrings(refsForCodeTrailEntry(entry).map(codeTrailFilePath)),
|
|
283
|
+
refs: [entry.primaryRef.stableKey],
|
|
284
|
+
reasonCodes: uniqueStrings(["section:code_trail_entry", `entry:${entry.entryKind}`, `status:${entry.status}`])
|
|
285
|
+
}));
|
|
286
|
+
return [
|
|
287
|
+
{
|
|
288
|
+
chapterId: stableId("code-story-chapter", [storySeed, "code-refs"]),
|
|
289
|
+
title: "CodeTrail Code Refs",
|
|
290
|
+
summary: `${fileRefsForStory.length} code refs were found in the Trail projection/import.`,
|
|
291
|
+
sections: fileSections,
|
|
292
|
+
reasonCodes: ["chapter:code_trail_code_refs"]
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
chapterId: stableId("code-story-chapter", [storySeed, "trail-evidence"]),
|
|
296
|
+
title: "Trail Evidence",
|
|
297
|
+
summary: `${trail.entries.length} entries remain report-only evidence and grant no write authority.`,
|
|
298
|
+
sections: evidenceSections,
|
|
299
|
+
reasonCodes: ["chapter:code_trail_evidence", "authority:report_only_no_action"]
|
|
300
|
+
}
|
|
301
|
+
];
|
|
302
|
+
}
|
|
303
|
+
function buildCodeTrailSourceReceipts(trail, generatedAt) {
|
|
304
|
+
return trail.sources.slice(0, 12).map((source) => ({
|
|
305
|
+
schemaVersion: 1,
|
|
306
|
+
source: source.kind.startsWith("local_mcp") ? "local_mcp" : "code_insights",
|
|
307
|
+
authority: "advisory_evidence",
|
|
308
|
+
status: "used",
|
|
309
|
+
generatedAt,
|
|
310
|
+
observedAt: source.observedAt,
|
|
311
|
+
confidence: {
|
|
312
|
+
score: typeof source.confidence === "number" ? Math.max(0, Math.min(1, source.confidence)) : 0.7,
|
|
313
|
+
label: "medium",
|
|
314
|
+
reasonCodes: ["source:code_trail"]
|
|
315
|
+
},
|
|
316
|
+
reasonCodes: uniqueStrings(["source:code_trail", source.kind ? `trail_source:${source.kind}` : undefined]),
|
|
317
|
+
details: {
|
|
318
|
+
sourceKey: source.key,
|
|
319
|
+
watermark: source.watermark
|
|
320
|
+
},
|
|
321
|
+
reportOnly: true,
|
|
322
|
+
advisoryOnly: true,
|
|
323
|
+
noAuthority: true
|
|
324
|
+
}));
|
|
325
|
+
}
|
|
326
|
+
function codeTrailStoryStatus(trail) {
|
|
327
|
+
if (trail.entries.some((entry) => entry.status === "blocked" || entry.status === "failed"))
|
|
328
|
+
return "blocked";
|
|
329
|
+
if (trail.entries.some((entry) => entry.status === "review_needed" || entry.status === "waiting"))
|
|
330
|
+
return "needs_review";
|
|
331
|
+
if (trail.entries.some((entry) => ["ready", "completed", "succeeded", "approved", "canonical"].includes(entry.status)))
|
|
332
|
+
return "ready";
|
|
333
|
+
return "unknown";
|
|
334
|
+
}
|
|
335
|
+
function codeTrailProofStatus(trail, fileCount) {
|
|
336
|
+
if (trail.entries.some((entry) => entry.status === "blocked" || entry.status === "failed"))
|
|
337
|
+
return "blocked";
|
|
338
|
+
if (trail.gaps.some((gap) => gap.severity === "critical" || gap.severity === "warning"))
|
|
339
|
+
return "needs_proof";
|
|
340
|
+
if (trail.entries.some((entry) => entry.status === "approved" || entry.status === "canonical"))
|
|
341
|
+
return "verified";
|
|
342
|
+
if (fileCount > 0)
|
|
343
|
+
return "partial";
|
|
344
|
+
return "unknown";
|
|
345
|
+
}
|
|
346
|
+
function latestTrailUpdatedAt(trail) {
|
|
347
|
+
const candidates = [
|
|
348
|
+
...trail.entries.flatMap((entry) => [entry.updatedAt, entry.completedAt, entry.createdAt]),
|
|
349
|
+
...trail.sources.map((source) => source.observedAt),
|
|
350
|
+
...trail.sourceWatermarks.map((watermark) => watermark.updatedAt)
|
|
351
|
+
].filter((value) => typeof value === "number" && Number.isFinite(value));
|
|
352
|
+
return candidates.length > 0 ? Math.max(...candidates) : undefined;
|
|
353
|
+
}
|
|
354
|
+
function buildStoryHeadline(story) {
|
|
355
|
+
const title = story.pr.title?.trim();
|
|
356
|
+
const number = typeof story.pr.number === "number" ? `#${story.pr.number}` : undefined;
|
|
357
|
+
if (title && number)
|
|
358
|
+
return `${number} ${title}`;
|
|
359
|
+
if (title)
|
|
360
|
+
return title;
|
|
361
|
+
if (number)
|
|
362
|
+
return `${number} code change story`;
|
|
363
|
+
return story.topMotion.title || "Code change story";
|
|
364
|
+
}
|
|
365
|
+
function buildSourceRefs(story) {
|
|
366
|
+
return uniqueRefs([
|
|
367
|
+
storyRef("pr_review_story", story.storyId, "PR Review Story", ["source:pr_review_story"]),
|
|
368
|
+
story.pr.prSnapshotId ? storyRef("pr_snapshot", story.pr.prSnapshotId, "PR snapshot", ["source:pr_snapshot"]) : undefined,
|
|
369
|
+
story.reviewDiffId ? storyRef("review_diff", story.reviewDiffId, "Review diff", ["source:review_diff"]) : undefined,
|
|
370
|
+
story.reviewRoomId ? storyRef("review_room", story.reviewRoomId, "Review room", ["source:review_room"]) : undefined,
|
|
371
|
+
story.impactMap?.impactMapId ? storyRef("impact_map", story.impactMap.impactMapId, "Impact map", ["source:impact_map"]) : undefined,
|
|
372
|
+
...story.sourceReceipts.map((receipt) => storyRef("receipt", `${receipt.source}:${receipt.scope}`, receipt.source, receipt.reasonCodes))
|
|
373
|
+
]);
|
|
374
|
+
}
|
|
375
|
+
function buildContextRefs(story) {
|
|
376
|
+
return uniqueRefs([
|
|
377
|
+
...story.keyFiles.map((file) => storyRef("file", file.path, file.path, file.reasonCodes, file.path, undefined, file.status)),
|
|
378
|
+
...story.proofGaps.flatMap((gap) => gap.refs.map((ref) => storyRef("proof_gap", ref, gap.title, ["proof:gap"]))),
|
|
379
|
+
...story.blockers.flatMap((blocker) => blocker.refs.map((ref) => storyRef("blocker", ref, blocker.title, ["blocker:present"]))),
|
|
380
|
+
...story.failedChecks.flatMap((check) => check.proofRefs.map((ref) => storyRef("failed_check", ref, check.name ?? check.checkId, ["check:failed"]))),
|
|
381
|
+
...story.impactMap?.impactedRefs.docs.map((ref) => storyRef("document", ref, ref, ["impact:document"])) ?? [],
|
|
382
|
+
...story.impactMap?.impactedRefs.entities.map((ref) => storyRef("entity", ref, ref, ["impact:entity"])) ?? [],
|
|
383
|
+
...story.impactMap?.impactedRefs.memories.map((ref) => storyRef("memory", ref, ref, ["impact:memory"])) ?? [],
|
|
384
|
+
...story.impactMap?.impactedRefs.relationships.map((ref) => storyRef("relationship", ref, ref, ["impact:relationship"])) ?? []
|
|
385
|
+
]);
|
|
386
|
+
}
|
|
387
|
+
function buildStoryChapters(story, reviewRoomState, reviewDiff) {
|
|
388
|
+
const chapters = [];
|
|
389
|
+
const reviewChapterSections = buildReviewDiffStoryChapters(story, reviewDiff);
|
|
390
|
+
chapters.push(...reviewChapterSections);
|
|
391
|
+
const changeSections = reviewChapterSections.length > 0 ? [] : story.keyFiles.slice(0, 8).map((file) => ({
|
|
392
|
+
sectionId: stableId("code-story-section", ["file", file.path, file.status]),
|
|
393
|
+
title: file.path,
|
|
394
|
+
summary: `${file.status} · ${file.additions} additions / ${file.deletions} deletions`,
|
|
395
|
+
filePaths: [file.path],
|
|
396
|
+
refs: [],
|
|
397
|
+
reasonCodes: uniqueStrings([...file.reasonCodes ?? [], `risk:${file.riskLevel}`])
|
|
398
|
+
}));
|
|
399
|
+
if (changeSections.length > 0) {
|
|
400
|
+
chapters.push({
|
|
401
|
+
chapterId: stableId("code-story-chapter", [story.storyId, "changed-files"]),
|
|
402
|
+
title: "Changed Files",
|
|
403
|
+
summary: `${story.counts.changedFiles} files are part of this review story.`,
|
|
404
|
+
sections: changeSections,
|
|
405
|
+
reasonCodes: ["chapter:changed_files"]
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
const proofSections = [
|
|
409
|
+
...story.blockers.map((blocker) => findingSection("blocker", blocker)),
|
|
410
|
+
...story.proofGaps.map((gap) => findingSection("proof_gap", gap)),
|
|
411
|
+
...story.failedChecks.map((check) => ({
|
|
412
|
+
sectionId: stableId("code-story-section", ["failed_check", check.checkId]),
|
|
413
|
+
title: check.name ?? check.checkId,
|
|
414
|
+
summary: check.conclusion ?? "Check failed or needs attention",
|
|
415
|
+
filePaths: check.mappedPaths,
|
|
416
|
+
refs: check.proofRefs,
|
|
417
|
+
reasonCodes: ["check:failed"]
|
|
418
|
+
}))
|
|
419
|
+
];
|
|
420
|
+
if (proofSections.length > 0) {
|
|
421
|
+
chapters.push({
|
|
422
|
+
chapterId: stableId("code-story-chapter", [story.storyId, "proof-and-risk"]),
|
|
423
|
+
title: "Proof And Risk",
|
|
424
|
+
summary: `${story.counts.blockers} blockers and ${story.counts.proofGaps} proof gaps are linked to receipts.`,
|
|
425
|
+
sections: proofSections.slice(0, 12),
|
|
426
|
+
reasonCodes: ["chapter:proof_and_risk"]
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
const reviewOrder = arrayValue(recordValue(reviewRoomState)["reviewOrder"]);
|
|
430
|
+
const reviewSections = reviewOrder.slice(0, 8).map((item, index) => {
|
|
431
|
+
const record = recordValue(item);
|
|
432
|
+
const path = stringValue(record["path"]) ?? `review-step:${index + 1}`;
|
|
433
|
+
return {
|
|
434
|
+
sectionId: stableId("code-story-section", ["review_order", path, String(index)]),
|
|
435
|
+
title: path,
|
|
436
|
+
summary: stringValue(record["reason"]) ?? "Suggested review order",
|
|
437
|
+
filePaths: path.startsWith("review-step:") ? [] : [path],
|
|
438
|
+
refs: stringArray(record["evidenceRefs"]),
|
|
439
|
+
reasonCodes: stringArray(record["reasonCodes"]).length > 0 ? stringArray(record["reasonCodes"]) : ["review:ordered_step"]
|
|
440
|
+
};
|
|
441
|
+
});
|
|
442
|
+
if (reviewSections.length > 0 || story.relatedPrs.length > 0) {
|
|
443
|
+
chapters.push({
|
|
444
|
+
chapterId: stableId("code-story-chapter", [story.storyId, "review-continuity"]),
|
|
445
|
+
title: "Review Continuity",
|
|
446
|
+
summary: `${reviewSections.length} ordered files and ${story.relatedPrs.length} Precedent refs are available.`,
|
|
447
|
+
sections: reviewSections,
|
|
448
|
+
reasonCodes: ["chapter:review_continuity"]
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
return chapters;
|
|
452
|
+
}
|
|
453
|
+
function buildReviewDiffStoryChapters(story, reviewDiff) {
|
|
454
|
+
const chapters = arrayValue(recordValue(reviewDiff)["reviewChapters"]);
|
|
455
|
+
if (chapters.length > 0) {
|
|
456
|
+
return chapters.flatMap((item, index) => {
|
|
457
|
+
const chapter = recordValue(item);
|
|
458
|
+
const filePaths = stringArray(chapter["filePaths"]);
|
|
459
|
+
if (filePaths.length === 0)
|
|
460
|
+
return [];
|
|
461
|
+
const title = stringValue(chapter["title"]) ?? `Review chapter ${index + 1}`;
|
|
462
|
+
const lineCount = typeof chapter["lineCount"] === "number" ? chapter["lineCount"] : undefined;
|
|
463
|
+
const fileCount = typeof chapter["fileCount"] === "number" ? chapter["fileCount"] : filePaths.length;
|
|
464
|
+
return [{
|
|
465
|
+
chapterId: stringValue(chapter["chapterId"]) ?? stableId("code-story-chapter", [story.storyId, "review-chapter", title, filePaths.join("|")]),
|
|
466
|
+
title,
|
|
467
|
+
summary: lineCount !== undefined ? `${fileCount} files, ${lineCount} changed lines.` : `${fileCount} files are grouped for review.`,
|
|
468
|
+
sections: [{
|
|
469
|
+
sectionId: stableId("code-story-section", ["review_chapter", title, filePaths.join("|")]),
|
|
470
|
+
title,
|
|
471
|
+
summary: stringArray(chapter["reasonCodes"]).slice(0, 3).join("; ") || "Suggested review chapter.",
|
|
472
|
+
filePaths,
|
|
473
|
+
refs: filePaths.slice(0, 20).map((path) => `file:${path}`),
|
|
474
|
+
reasonCodes: stringArray(chapter["reasonCodes"]).length > 0 ? stringArray(chapter["reasonCodes"]) : ["chapter:review_diff"]
|
|
475
|
+
}],
|
|
476
|
+
reasonCodes: uniqueStrings(["chapter:review_diff", ...stringArray(chapter["reasonCodes"])])
|
|
477
|
+
}];
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
const walkthroughOrder = arrayValue(recordValue(reviewDiff)["walkthroughOrder"]);
|
|
481
|
+
return walkthroughOrder.flatMap((item, index) => {
|
|
482
|
+
const group = recordValue(item);
|
|
483
|
+
const filePaths = stringArray(group["filePaths"]);
|
|
484
|
+
if (filePaths.length === 0)
|
|
485
|
+
return [];
|
|
486
|
+
const title = stringValue(group["title"]) ?? `Review chapter ${index + 1}`;
|
|
487
|
+
const reasonCodes = stringArray(group["reasonCodes"]);
|
|
488
|
+
return [{
|
|
489
|
+
chapterId: stringValue(group["groupId"]) ?? stableId("code-story-chapter", [story.storyId, "walkthrough", title, filePaths.join("|")]),
|
|
490
|
+
title,
|
|
491
|
+
summary: `${filePaths.length} files are grouped for review.`,
|
|
492
|
+
sections: [{
|
|
493
|
+
sectionId: stableId("code-story-section", ["walkthrough", title, filePaths.join("|")]),
|
|
494
|
+
title,
|
|
495
|
+
summary: reasonCodes.slice(0, 3).join("; ") || "Suggested walkthrough group.",
|
|
496
|
+
filePaths,
|
|
497
|
+
refs: filePaths.slice(0, 20).map((path) => `file:${path}`),
|
|
498
|
+
reasonCodes: reasonCodes.length > 0 ? reasonCodes : ["chapter:walkthrough_order"]
|
|
499
|
+
}],
|
|
500
|
+
reasonCodes: uniqueStrings(["chapter:walkthrough_order", ...reasonCodes])
|
|
501
|
+
}];
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
function buildStoryNextActions(story) {
|
|
505
|
+
const motion = story.topMotion;
|
|
506
|
+
return [{
|
|
507
|
+
actionId: stableId("code-story-action", [story.storyId, motion.kind, motion.title]),
|
|
508
|
+
kind: actionKindFromMotion(motion.kind),
|
|
509
|
+
title: motion.title,
|
|
510
|
+
detail: motion.detail,
|
|
511
|
+
filePaths: motion.filePaths ?? [],
|
|
512
|
+
refs: motion.refs ?? [],
|
|
513
|
+
reasonCodes: ["authority:advisory_only", `motion:${motion.kind}`]
|
|
514
|
+
}];
|
|
515
|
+
}
|
|
516
|
+
function findingSection(prefix, finding) {
|
|
517
|
+
return {
|
|
518
|
+
sectionId: stableId("code-story-section", [prefix, finding.title, finding.refs.join("|")]),
|
|
519
|
+
title: finding.title,
|
|
520
|
+
summary: finding.detail ?? finding.kind,
|
|
521
|
+
filePaths: finding.filePaths,
|
|
522
|
+
refs: finding.refs,
|
|
523
|
+
reasonCodes: [`${prefix}:present`, `finding:${finding.kind}`]
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
function proofStatusForStory(story) {
|
|
527
|
+
if (story.counts.blockers > 0 || story.counts.failedChecks > 0 || story.status === "blocked")
|
|
528
|
+
return "blocked";
|
|
529
|
+
if (story.counts.proofGaps > 0 || story.status === "needs_review")
|
|
530
|
+
return "needs_proof";
|
|
531
|
+
if (story.status === "ready")
|
|
532
|
+
return "verified";
|
|
533
|
+
if (story.keyFiles.length > 0)
|
|
534
|
+
return "partial";
|
|
535
|
+
return "unknown";
|
|
536
|
+
}
|
|
537
|
+
function actionKindFromMotion(kind) {
|
|
538
|
+
switch (kind) {
|
|
539
|
+
case "resolve_blockers":
|
|
540
|
+
return "resolve_blocker";
|
|
541
|
+
case "refresh_review":
|
|
542
|
+
return "refresh_review";
|
|
543
|
+
case "attach_validation":
|
|
544
|
+
return "attach_proof";
|
|
545
|
+
case "review_files":
|
|
546
|
+
return "review_next_file";
|
|
547
|
+
case "merge_decision":
|
|
548
|
+
return "record_review_decision";
|
|
549
|
+
case "collect_review_evidence":
|
|
550
|
+
default:
|
|
551
|
+
return "collect_evidence";
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
function reviewContinuityStatus(args) {
|
|
555
|
+
if (args.counts.blockerCount > 0 || args.counts.failedCheckCount > 0 || args.story?.status === "blocked")
|
|
556
|
+
return "blocked";
|
|
557
|
+
if (args.counts.staleApprovalCount > 0 || args.staleApproval["state"] === "stale" || args.story?.freshness.status === "stale" || args.story?.status === "stale")
|
|
558
|
+
return "stale";
|
|
559
|
+
if (args.counts.proofGapCount > 0 || args.story?.status === "needs_review")
|
|
560
|
+
return "needs_proof";
|
|
561
|
+
if (args.story?.status === "ready" || args.counts.currentApprovalCount > 0)
|
|
562
|
+
return "ready";
|
|
563
|
+
return "fresh";
|
|
564
|
+
}
|
|
565
|
+
function continuityNextAction(args) {
|
|
566
|
+
const firstBlocker = firstRecord(args.blockers);
|
|
567
|
+
const firstProofGap = firstRecord(args.proofGaps);
|
|
568
|
+
const firstFailedCheck = firstRecord(args.failedChecks);
|
|
569
|
+
const reviewOrder = arrayValue(args.reviewRoomState["reviewOrder"]);
|
|
570
|
+
const firstReviewFile = firstRecord(reviewOrder);
|
|
571
|
+
const fallbackRefs = args.story?.topMotion.refs ?? [];
|
|
572
|
+
const fallbackFiles = args.story?.topMotion.filePaths ?? [];
|
|
573
|
+
if (args.status === "blocked") {
|
|
574
|
+
const title = stringValue(firstBlocker["title"]) ?? stringValue(firstFailedCheck["name"]) ?? "Resolve blocking review evidence";
|
|
575
|
+
return continuityAction("resolve_blocker", title, "Clear the blocker or failed-check evidence before this review can move forward.", stringArray(firstBlocker["filePaths"]).concat(stringArray(firstFailedCheck["mappedPaths"])), stringArray(firstBlocker["refs"]).concat(stringArray(firstFailedCheck["proofRefs"]), fallbackRefs), ["next_action:resolve_blocker"]);
|
|
576
|
+
}
|
|
577
|
+
if (args.status === "stale") {
|
|
578
|
+
return continuityAction("refresh_review", "Refresh review against the current head", "Approval or story freshness is stale; rebuild the read-only review evidence before taking a decision.", fallbackFiles, fallbackRefs, ["next_action:refresh_review", "authority:no_github_write"]);
|
|
579
|
+
}
|
|
580
|
+
if (args.status === "needs_proof") {
|
|
581
|
+
return continuityAction("attach_proof", stringValue(firstProofGap["title"]) ?? "Attach missing review proof", "Add validation, check, or source receipt evidence for the open proof gap.", stringArray(firstProofGap["filePaths"]).concat(fallbackFiles), stringArray(firstProofGap["refs"]).concat(fallbackRefs), ["next_action:attach_proof"]);
|
|
582
|
+
}
|
|
583
|
+
if (args.status === "ready") {
|
|
584
|
+
return continuityAction("record_review_decision", "Review final evidence and record a decision", "The read-time projection is ready; this does not post to GitHub, approve, merge, apply code, or promote memory.", fallbackFiles, fallbackRefs, ["next_action:record_review_decision", "authority:no_merge_or_approval"]);
|
|
585
|
+
}
|
|
586
|
+
const firstPath = stringValue(firstReviewFile["path"]);
|
|
587
|
+
return continuityAction("continue_review", firstPath ? `Review ${firstPath}` : "Continue the next review step", "Open the next ordered file or receipt-backed story evidence.", firstPath ? [firstPath] : fallbackFiles, fallbackRefs, ["next_action:continue_review"]);
|
|
588
|
+
}
|
|
589
|
+
function continuityAction(kind, title, detail, filePaths, refs, reasonCodes) {
|
|
590
|
+
return {
|
|
591
|
+
actionId: stableId("pr-review-continuity-action", [kind, title, refs.join("|")]),
|
|
592
|
+
kind,
|
|
593
|
+
title,
|
|
594
|
+
detail,
|
|
595
|
+
filePaths: uniqueStrings(filePaths),
|
|
596
|
+
refs: uniqueStrings(refs),
|
|
597
|
+
reasonCodes: uniqueStrings([...reasonCodes, "authority:report_only_no_action"])
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
function storyRef(kind, id, label, reasonCodes, path, href, status) {
|
|
601
|
+
return { kind, id, label, path, href, status, reasonCodes: uniqueStrings(reasonCodes) };
|
|
602
|
+
}
|
|
603
|
+
function uniqueRefs(refs) {
|
|
604
|
+
const seen = new Set;
|
|
605
|
+
const output = [];
|
|
606
|
+
for (const ref of refs) {
|
|
607
|
+
if (!ref?.id?.trim())
|
|
608
|
+
continue;
|
|
609
|
+
const key = `${ref.kind}:${ref.id}`;
|
|
610
|
+
if (seen.has(key))
|
|
611
|
+
continue;
|
|
612
|
+
seen.add(key);
|
|
613
|
+
output.push({ ...ref, id: ref.id.trim() });
|
|
614
|
+
}
|
|
615
|
+
return output;
|
|
616
|
+
}
|
|
617
|
+
function firstRecord(values) {
|
|
618
|
+
return recordValue(values[0]);
|
|
619
|
+
}
|
|
620
|
+
function recordValue(value) {
|
|
621
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
622
|
+
}
|
|
623
|
+
function arrayValue(value) {
|
|
624
|
+
return Array.isArray(value) ? value : [];
|
|
625
|
+
}
|
|
626
|
+
function stringValue(value) {
|
|
627
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
628
|
+
}
|
|
629
|
+
function stringArray(value) {
|
|
630
|
+
return Array.isArray(value) ? value.filter(isString) : [];
|
|
631
|
+
}
|
|
632
|
+
function isString(value) {
|
|
633
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
634
|
+
}
|
|
635
|
+
function numberValue(value, fallback) {
|
|
636
|
+
return typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
637
|
+
}
|
|
638
|
+
function stableId(prefix, parts) {
|
|
639
|
+
return `${prefix}:${stableHash(parts.filter(Boolean).join("|")).slice(0, 12)}`;
|
|
640
|
+
}
|
|
641
|
+
function stableHash(input) {
|
|
642
|
+
let hash = 2166136261;
|
|
643
|
+
for (let index = 0;index < input.length; index += 1) {
|
|
644
|
+
hash ^= input.charCodeAt(index);
|
|
645
|
+
hash = Math.imul(hash, 16777619);
|
|
646
|
+
}
|
|
647
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
648
|
+
}
|
|
649
|
+
function uniqueStrings(values) {
|
|
650
|
+
const seen = new Set;
|
|
651
|
+
const output = [];
|
|
652
|
+
for (const value of values) {
|
|
653
|
+
const trimmed = value?.trim();
|
|
654
|
+
if (!trimmed || seen.has(trimmed))
|
|
655
|
+
continue;
|
|
656
|
+
seen.add(trimmed);
|
|
657
|
+
output.push(trimmed);
|
|
658
|
+
}
|
|
659
|
+
return output;
|
|
660
|
+
}
|
|
661
|
+
export {
|
|
662
|
+
codeChangeStoryNoAuthorityBoundaryV1,
|
|
663
|
+
buildReviewContinuityV1,
|
|
664
|
+
buildCodeChangeStoryFromPrReviewV1,
|
|
665
|
+
buildCodeChangeStoryFromCodeTrailV1,
|
|
666
|
+
CODE_CHANGE_STORY_SCHEMA_VERSION
|
|
667
|
+
};
|