vouchington-tooling 0.5.0 โ 0.6.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/ast-grep-examples/index.mjs +1 -1
- package/dist/gha-pr-checkpoint/codec.d.mts +58 -0
- package/dist/gha-pr-checkpoint/codec.mjs +100 -0
- package/dist/gha-pr-checkpoint/index.d.mts +3 -58
- package/dist/gha-pr-checkpoint/index.mjs +3 -100
- package/dist/gha-pr-checkpoint/resume.d.mts +14 -0
- package/dist/gha-pr-checkpoint/resume.mjs +21 -0
- package/dist/gha-pr-checkpoint/update.d.mts +15 -0
- package/dist/gha-pr-checkpoint/update.mjs +30 -0
- package/package.json +1 -1
- package/skills/retrospective-distill/SKILL.md +8 -2
|
@@ -86,7 +86,7 @@ function scanScopedRule(loaded, root, languageGlobs, execute) {
|
|
|
86
86
|
}
|
|
87
87
|
fs.copyFileSync(loaded.ruleFile, join(root, 'rule.yml'));
|
|
88
88
|
fs.writeFileSync(join(root, 'sgconfig.yml'), yamlDump({ languageGlobs }));
|
|
89
|
-
const result = execute(['scan', '--rule', 'rule.yml', '--config', 'sgconfig.yml', '--json', '--no-ignore', '
|
|
89
|
+
const result = execute(['scan', '--rule', 'rule.yml', '--config', 'sgconfig.yml', '--json', '--no-ignore', 'hidden'], root);
|
|
90
90
|
if (result.status === null || result.status > 1)
|
|
91
91
|
throw new Error(`ast-grep scan failed (exit ${result.status ?? 'signal'}): ${result.stderr ?? ''}`);
|
|
92
92
|
const found = new Set(result.stdout?.trim()
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export declare const CHECKPOINT_MARKER = "pr-checkpoint:v1";
|
|
2
|
+
export type CheckpointStatus = 'queued' | 'running' | 'awaiting_verification' | 'failed' | 'deadline' | 'unresumable' | 'complete';
|
|
3
|
+
export type Checkpoint = {
|
|
4
|
+
marker: string;
|
|
5
|
+
repository: string;
|
|
6
|
+
pr: number;
|
|
7
|
+
/** Absent on checkpoints posted before trigger ids were recorded; keep optional so resume can still parse them. */
|
|
8
|
+
triggerCommentId?: number;
|
|
9
|
+
headRef: string;
|
|
10
|
+
startSha: string;
|
|
11
|
+
sessionStartSha: string;
|
|
12
|
+
runId: string;
|
|
13
|
+
runUrl: string;
|
|
14
|
+
actor: string;
|
|
15
|
+
sessionId: string;
|
|
16
|
+
/** Absent from v1 checkpoints created before session URLs were exposed. */
|
|
17
|
+
sessionUrl?: string;
|
|
18
|
+
resumeSourceRunId: string;
|
|
19
|
+
status: CheckpointStatus;
|
|
20
|
+
createdAt: string;
|
|
21
|
+
updatedAt: string;
|
|
22
|
+
};
|
|
23
|
+
export type GitHubComment = {
|
|
24
|
+
id: number;
|
|
25
|
+
user?: {
|
|
26
|
+
login?: string;
|
|
27
|
+
type?: string;
|
|
28
|
+
};
|
|
29
|
+
performed_via_github_app?: {
|
|
30
|
+
slug?: string;
|
|
31
|
+
} | null;
|
|
32
|
+
body?: string;
|
|
33
|
+
created_at?: string;
|
|
34
|
+
};
|
|
35
|
+
export interface CheckpointCodecOptions {
|
|
36
|
+
marker?: string;
|
|
37
|
+
sessionIdPattern?: RegExp;
|
|
38
|
+
}
|
|
39
|
+
export interface TrustedCheckpointActor {
|
|
40
|
+
actor: string;
|
|
41
|
+
userType?: string;
|
|
42
|
+
appSlug?: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Anti-forgery predicate: a checkpoint HTML comment is plain text any PR commenter can paste,
|
|
46
|
+
* so `parseCheckpoint` alone proves shape, not provenance. This proves the comment itself was
|
|
47
|
+
* authored by the expected App identity.
|
|
48
|
+
*/
|
|
49
|
+
export declare function isTrustedCheckpointComment(comment: GitHubComment, context: TrustedCheckpointActor): boolean;
|
|
50
|
+
export declare function renderCheckpoint(checkpoint: Checkpoint, options?: {
|
|
51
|
+
marker?: string;
|
|
52
|
+
}): string;
|
|
53
|
+
export declare function parseCheckpoint(body: string, options?: CheckpointCodecOptions): Checkpoint | undefined;
|
|
54
|
+
export declare function validateCheckpoint(value: unknown, options?: CheckpointCodecOptions): Checkpoint | undefined;
|
|
55
|
+
export declare function sortedCheckpointCandidates(comments: GitHubComment[], options?: CheckpointCodecOptions): {
|
|
56
|
+
comment: GitHubComment;
|
|
57
|
+
checkpoint: Checkpoint;
|
|
58
|
+
}[];
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
export const CHECKPOINT_MARKER = 'pr-checkpoint:v1';
|
|
2
|
+
/**
|
|
3
|
+
* Anti-forgery predicate: a checkpoint HTML comment is plain text any PR commenter can paste,
|
|
4
|
+
* so `parseCheckpoint` alone proves shape, not provenance. This proves the comment itself was
|
|
5
|
+
* authored by the expected App identity.
|
|
6
|
+
*/
|
|
7
|
+
export function isTrustedCheckpointComment(comment, context) {
|
|
8
|
+
return (comment.user?.login === context.actor &&
|
|
9
|
+
comment.user?.type === (context.userType ?? 'Bot') &&
|
|
10
|
+
comment.performed_via_github_app?.slug === (context.appSlug ?? 'github-actions'));
|
|
11
|
+
}
|
|
12
|
+
export function renderCheckpoint(checkpoint, options = {}) {
|
|
13
|
+
const marker = options.marker ?? checkpoint.marker;
|
|
14
|
+
const payload = Buffer.from(JSON.stringify({ ...checkpoint, marker }), 'utf8').toString('base64url');
|
|
15
|
+
const session = checkpoint.sessionUrl
|
|
16
|
+
? `[${checkpoint.sessionId}](${checkpoint.sessionUrl})`
|
|
17
|
+
: `\`${checkpoint.sessionId || 'pending'}\``;
|
|
18
|
+
return [
|
|
19
|
+
`๐ [View the automation workflow run](${checkpoint.runUrl}).`,
|
|
20
|
+
'',
|
|
21
|
+
`Status: \`${checkpoint.status}\` ยท Session: ${session}`,
|
|
22
|
+
'',
|
|
23
|
+
`<!-- ${marker} ${payload} -->`,
|
|
24
|
+
].join('\n');
|
|
25
|
+
}
|
|
26
|
+
export function parseCheckpoint(body, options = {}) {
|
|
27
|
+
const marker = options.marker ?? CHECKPOINT_MARKER;
|
|
28
|
+
const match = body.match(new RegExp(`<!-- ${escapeRegExp(marker)} ([A-Za-z0-9_-]+) -->`, 'u'));
|
|
29
|
+
if (!match?.[1])
|
|
30
|
+
return undefined;
|
|
31
|
+
try {
|
|
32
|
+
return validateCheckpoint(JSON.parse(Buffer.from(match[1], 'base64url').toString('utf8')), options);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function validateCheckpoint(value, options = {}) {
|
|
39
|
+
if (!value || typeof value !== 'object')
|
|
40
|
+
return undefined;
|
|
41
|
+
const checkpoint = value;
|
|
42
|
+
const marker = options.marker ?? CHECKPOINT_MARKER;
|
|
43
|
+
if (checkpoint.marker !== marker ||
|
|
44
|
+
!Number.isInteger(checkpoint.pr) ||
|
|
45
|
+
(checkpoint.triggerCommentId !== undefined && !Number.isInteger(checkpoint.triggerCommentId)) ||
|
|
46
|
+
typeof checkpoint.startSha !== 'string' ||
|
|
47
|
+
!/^[0-9a-f]{40}$/u.test(checkpoint.startSha) ||
|
|
48
|
+
typeof checkpoint.sessionStartSha !== 'string' ||
|
|
49
|
+
!/^[0-9a-f]{40}$/u.test(checkpoint.sessionStartSha) ||
|
|
50
|
+
typeof checkpoint.runId !== 'string' ||
|
|
51
|
+
!/^[0-9]+$/u.test(checkpoint.runId) ||
|
|
52
|
+
typeof checkpoint.resumeSourceRunId !== 'string' ||
|
|
53
|
+
(checkpoint.resumeSourceRunId !== '' && !/^[0-9]+$/u.test(checkpoint.resumeSourceRunId)) ||
|
|
54
|
+
typeof checkpoint.repository !== 'string' ||
|
|
55
|
+
typeof checkpoint.headRef !== 'string' ||
|
|
56
|
+
typeof checkpoint.runUrl !== 'string' ||
|
|
57
|
+
typeof checkpoint.actor !== 'string' ||
|
|
58
|
+
typeof checkpoint.sessionId !== 'string' ||
|
|
59
|
+
typeof checkpoint.createdAt !== 'string' ||
|
|
60
|
+
!Number.isFinite(Date.parse(checkpoint.createdAt)) ||
|
|
61
|
+
typeof checkpoint.updatedAt !== 'string' ||
|
|
62
|
+
!Number.isFinite(Date.parse(checkpoint.updatedAt)) ||
|
|
63
|
+
typeof checkpoint.status !== 'string' ||
|
|
64
|
+
![
|
|
65
|
+
'queued',
|
|
66
|
+
'running',
|
|
67
|
+
'awaiting_verification',
|
|
68
|
+
'failed',
|
|
69
|
+
'deadline',
|
|
70
|
+
'unresumable',
|
|
71
|
+
'complete',
|
|
72
|
+
].includes(checkpoint.status)) {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
if (checkpoint.sessionId &&
|
|
76
|
+
options.sessionIdPattern &&
|
|
77
|
+
!matchesPattern(options.sessionIdPattern, checkpoint.sessionId)) {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
if (checkpoint.sessionUrl !== undefined &&
|
|
81
|
+
(!URL.canParse(checkpoint.sessionUrl) || !checkpoint.sessionUrl.startsWith('https:'))) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
return checkpoint;
|
|
85
|
+
}
|
|
86
|
+
export function sortedCheckpointCandidates(comments, options = {}) {
|
|
87
|
+
return comments
|
|
88
|
+
.map((comment) => ({ comment, checkpoint: parseCheckpoint(comment.body ?? '', options) }))
|
|
89
|
+
.filter((candidate) => candidate.checkpoint !== undefined)
|
|
90
|
+
.toSorted((left, right) => {
|
|
91
|
+
const timestampOrder = String(right.comment.created_at ?? '').localeCompare(String(left.comment.created_at ?? ''));
|
|
92
|
+
return timestampOrder || right.comment.id - left.comment.id;
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function matchesPattern(pattern, value) {
|
|
96
|
+
return new RegExp(pattern.source, pattern.flags.replaceAll(/[gy]/g, '')).test(value);
|
|
97
|
+
}
|
|
98
|
+
function escapeRegExp(value) {
|
|
99
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
100
|
+
}
|
|
@@ -1,58 +1,3 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export type
|
|
4
|
-
marker: string;
|
|
5
|
-
repository: string;
|
|
6
|
-
pr: number;
|
|
7
|
-
/** Absent on checkpoints posted before trigger ids were recorded; keep optional so resume can still parse them. */
|
|
8
|
-
triggerCommentId?: number;
|
|
9
|
-
headRef: string;
|
|
10
|
-
startSha: string;
|
|
11
|
-
sessionStartSha: string;
|
|
12
|
-
runId: string;
|
|
13
|
-
runUrl: string;
|
|
14
|
-
actor: string;
|
|
15
|
-
sessionId: string;
|
|
16
|
-
/** Absent from v1 checkpoints created before session URLs were exposed. */
|
|
17
|
-
sessionUrl?: string;
|
|
18
|
-
resumeSourceRunId: string;
|
|
19
|
-
status: CheckpointStatus;
|
|
20
|
-
createdAt: string;
|
|
21
|
-
updatedAt: string;
|
|
22
|
-
};
|
|
23
|
-
export type GitHubComment = {
|
|
24
|
-
id: number;
|
|
25
|
-
user?: {
|
|
26
|
-
login?: string;
|
|
27
|
-
type?: string;
|
|
28
|
-
};
|
|
29
|
-
performed_via_github_app?: {
|
|
30
|
-
slug?: string;
|
|
31
|
-
} | null;
|
|
32
|
-
body?: string;
|
|
33
|
-
created_at?: string;
|
|
34
|
-
};
|
|
35
|
-
export interface CheckpointCodecOptions {
|
|
36
|
-
marker?: string;
|
|
37
|
-
sessionIdPattern?: RegExp;
|
|
38
|
-
}
|
|
39
|
-
export interface TrustedCheckpointActor {
|
|
40
|
-
actor: string;
|
|
41
|
-
userType?: string;
|
|
42
|
-
appSlug?: string;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Anti-forgery predicate: a checkpoint HTML comment is plain text any PR commenter can paste,
|
|
46
|
-
* so `parseCheckpoint` alone proves shape, not provenance. This proves the comment itself was
|
|
47
|
-
* authored by the expected App identity.
|
|
48
|
-
*/
|
|
49
|
-
export declare function isTrustedCheckpointComment(comment: GitHubComment, context: TrustedCheckpointActor): boolean;
|
|
50
|
-
export declare function renderCheckpoint(checkpoint: Checkpoint, options?: {
|
|
51
|
-
marker?: string;
|
|
52
|
-
}): string;
|
|
53
|
-
export declare function parseCheckpoint(body: string, options?: CheckpointCodecOptions): Checkpoint | undefined;
|
|
54
|
-
export declare function validateCheckpoint(value: unknown, options?: CheckpointCodecOptions): Checkpoint | undefined;
|
|
55
|
-
export declare function sortedCheckpointCandidates(comments: GitHubComment[], options?: CheckpointCodecOptions): {
|
|
56
|
-
comment: GitHubComment;
|
|
57
|
-
checkpoint: Checkpoint;
|
|
58
|
-
}[];
|
|
1
|
+
export * from './codec.mts';
|
|
2
|
+
export { selectResumeCheckpoint, type CheckpointSelectionContext } from './resume.mts';
|
|
3
|
+
export { updateExactCheckpoint, type CheckpointUpdateContext } from './update.mts';
|
|
@@ -1,100 +1,3 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* so `parseCheckpoint` alone proves shape, not provenance. This proves the comment itself was
|
|
5
|
-
* authored by the expected App identity.
|
|
6
|
-
*/
|
|
7
|
-
export function isTrustedCheckpointComment(comment, context) {
|
|
8
|
-
return (comment.user?.login === context.actor &&
|
|
9
|
-
comment.user?.type === (context.userType ?? 'Bot') &&
|
|
10
|
-
comment.performed_via_github_app?.slug === (context.appSlug ?? 'github-actions'));
|
|
11
|
-
}
|
|
12
|
-
export function renderCheckpoint(checkpoint, options = {}) {
|
|
13
|
-
const marker = options.marker ?? checkpoint.marker;
|
|
14
|
-
const payload = Buffer.from(JSON.stringify({ ...checkpoint, marker }), 'utf8').toString('base64url');
|
|
15
|
-
const session = checkpoint.sessionUrl
|
|
16
|
-
? `[${checkpoint.sessionId}](${checkpoint.sessionUrl})`
|
|
17
|
-
: `\`${checkpoint.sessionId || 'pending'}\``;
|
|
18
|
-
return [
|
|
19
|
-
`๐ [View the automation workflow run](${checkpoint.runUrl}).`,
|
|
20
|
-
'',
|
|
21
|
-
`Status: \`${checkpoint.status}\` ยท Session: ${session}`,
|
|
22
|
-
'',
|
|
23
|
-
`<!-- ${marker} ${payload} -->`,
|
|
24
|
-
].join('\n');
|
|
25
|
-
}
|
|
26
|
-
export function parseCheckpoint(body, options = {}) {
|
|
27
|
-
const marker = options.marker ?? CHECKPOINT_MARKER;
|
|
28
|
-
const match = body.match(new RegExp(`<!-- ${escapeRegExp(marker)} ([A-Za-z0-9_-]+) -->`, 'u'));
|
|
29
|
-
if (!match?.[1])
|
|
30
|
-
return undefined;
|
|
31
|
-
try {
|
|
32
|
-
return validateCheckpoint(JSON.parse(Buffer.from(match[1], 'base64url').toString('utf8')), options);
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
return undefined;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
export function validateCheckpoint(value, options = {}) {
|
|
39
|
-
if (!value || typeof value !== 'object')
|
|
40
|
-
return undefined;
|
|
41
|
-
const checkpoint = value;
|
|
42
|
-
const marker = options.marker ?? CHECKPOINT_MARKER;
|
|
43
|
-
if (checkpoint.marker !== marker ||
|
|
44
|
-
!Number.isInteger(checkpoint.pr) ||
|
|
45
|
-
(checkpoint.triggerCommentId !== undefined && !Number.isInteger(checkpoint.triggerCommentId)) ||
|
|
46
|
-
typeof checkpoint.startSha !== 'string' ||
|
|
47
|
-
!/^[0-9a-f]{40}$/u.test(checkpoint.startSha) ||
|
|
48
|
-
typeof checkpoint.sessionStartSha !== 'string' ||
|
|
49
|
-
!/^[0-9a-f]{40}$/u.test(checkpoint.sessionStartSha) ||
|
|
50
|
-
typeof checkpoint.runId !== 'string' ||
|
|
51
|
-
!/^[0-9]+$/u.test(checkpoint.runId) ||
|
|
52
|
-
typeof checkpoint.resumeSourceRunId !== 'string' ||
|
|
53
|
-
(checkpoint.resumeSourceRunId !== '' && !/^[0-9]+$/u.test(checkpoint.resumeSourceRunId)) ||
|
|
54
|
-
typeof checkpoint.repository !== 'string' ||
|
|
55
|
-
typeof checkpoint.headRef !== 'string' ||
|
|
56
|
-
typeof checkpoint.runUrl !== 'string' ||
|
|
57
|
-
typeof checkpoint.actor !== 'string' ||
|
|
58
|
-
typeof checkpoint.sessionId !== 'string' ||
|
|
59
|
-
typeof checkpoint.createdAt !== 'string' ||
|
|
60
|
-
!Number.isFinite(Date.parse(checkpoint.createdAt)) ||
|
|
61
|
-
typeof checkpoint.updatedAt !== 'string' ||
|
|
62
|
-
!Number.isFinite(Date.parse(checkpoint.updatedAt)) ||
|
|
63
|
-
typeof checkpoint.status !== 'string' ||
|
|
64
|
-
![
|
|
65
|
-
'queued',
|
|
66
|
-
'running',
|
|
67
|
-
'awaiting_verification',
|
|
68
|
-
'failed',
|
|
69
|
-
'deadline',
|
|
70
|
-
'unresumable',
|
|
71
|
-
'complete',
|
|
72
|
-
].includes(checkpoint.status)) {
|
|
73
|
-
return undefined;
|
|
74
|
-
}
|
|
75
|
-
if (checkpoint.sessionId &&
|
|
76
|
-
options.sessionIdPattern &&
|
|
77
|
-
!matchesPattern(options.sessionIdPattern, checkpoint.sessionId)) {
|
|
78
|
-
return undefined;
|
|
79
|
-
}
|
|
80
|
-
if (checkpoint.sessionUrl !== undefined &&
|
|
81
|
-
(!URL.canParse(checkpoint.sessionUrl) || !checkpoint.sessionUrl.startsWith('https:'))) {
|
|
82
|
-
return undefined;
|
|
83
|
-
}
|
|
84
|
-
return checkpoint;
|
|
85
|
-
}
|
|
86
|
-
export function sortedCheckpointCandidates(comments, options = {}) {
|
|
87
|
-
return comments
|
|
88
|
-
.map((comment) => ({ comment, checkpoint: parseCheckpoint(comment.body ?? '', options) }))
|
|
89
|
-
.filter((candidate) => candidate.checkpoint !== undefined)
|
|
90
|
-
.toSorted((left, right) => {
|
|
91
|
-
const timestampOrder = String(right.comment.created_at ?? '').localeCompare(String(left.comment.created_at ?? ''));
|
|
92
|
-
return timestampOrder || right.comment.id - left.comment.id;
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
function matchesPattern(pattern, value) {
|
|
96
|
-
return new RegExp(pattern.source, pattern.flags.replaceAll(/[gy]/g, '')).test(value);
|
|
97
|
-
}
|
|
98
|
-
function escapeRegExp(value) {
|
|
99
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
100
|
-
}
|
|
1
|
+
export * from './codec.mjs';
|
|
2
|
+
export { selectResumeCheckpoint } from './resume.mjs';
|
|
3
|
+
export { updateExactCheckpoint } from './update.mjs';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Checkpoint, type GitHubComment } from './codec.mts';
|
|
2
|
+
export type CheckpointSelectionContext = {
|
|
3
|
+
repository: string;
|
|
4
|
+
pr: number;
|
|
5
|
+
headRef: string;
|
|
6
|
+
headSha: string;
|
|
7
|
+
actor: string;
|
|
8
|
+
isAncestor: (candidate: string, head: string) => boolean;
|
|
9
|
+
isShepherdRun: (runId: string) => boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function selectResumeCheckpoint(comments: GitHubComment[], context: CheckpointSelectionContext): {
|
|
12
|
+
checkpoint: Checkpoint;
|
|
13
|
+
commentId: number;
|
|
14
|
+
} | undefined;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { isTrustedCheckpointComment, sortedCheckpointCandidates, } from './codec.mjs';
|
|
2
|
+
export function selectResumeCheckpoint(comments, context) {
|
|
3
|
+
const candidates = sortedCheckpointCandidates(comments);
|
|
4
|
+
for (const { comment, checkpoint } of candidates) {
|
|
5
|
+
if (!isTrustedCheckpointComment(comment, context) ||
|
|
6
|
+
checkpoint.actor !== context.actor ||
|
|
7
|
+
checkpoint.repository !== context.repository ||
|
|
8
|
+
checkpoint.pr !== context.pr ||
|
|
9
|
+
checkpoint.headRef !== context.headRef ||
|
|
10
|
+
!checkpoint.sessionId ||
|
|
11
|
+
!context.isShepherdRun(checkpoint.runId) ||
|
|
12
|
+
!context.isAncestor(checkpoint.startSha, context.headSha) ||
|
|
13
|
+
!context.isAncestor(checkpoint.sessionStartSha, context.headSha)) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (checkpoint.status === 'complete' || checkpoint.status === 'unresumable')
|
|
17
|
+
return undefined;
|
|
18
|
+
return { checkpoint, commentId: comment.id };
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type CheckpointStatus, type GitHubComment } from './codec.mts';
|
|
2
|
+
export type CheckpointUpdateContext = {
|
|
3
|
+
actor: string;
|
|
4
|
+
commentId: number;
|
|
5
|
+
headRef: string;
|
|
6
|
+
headSha: string;
|
|
7
|
+
pr: number;
|
|
8
|
+
repository: string;
|
|
9
|
+
runId: string;
|
|
10
|
+
triggerCommentId: number;
|
|
11
|
+
};
|
|
12
|
+
export declare function updateExactCheckpoint(comment: GitHubComment, context: CheckpointUpdateContext, status: Extract<CheckpointStatus, 'failed' | 'running'>, session: {
|
|
13
|
+
id?: string;
|
|
14
|
+
url?: string;
|
|
15
|
+
}): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { isTrustedCheckpointComment, parseCheckpoint, renderCheckpoint, } from './codec.mjs';
|
|
2
|
+
export function updateExactCheckpoint(comment, context, status, session) {
|
|
3
|
+
const checkpoint = parseCheckpoint(comment.body ?? '');
|
|
4
|
+
if (!checkpoint ||
|
|
5
|
+
comment.id !== context.commentId ||
|
|
6
|
+
!isTrustedCheckpointComment(comment, context) ||
|
|
7
|
+
checkpoint.actor !== context.actor ||
|
|
8
|
+
checkpoint.repository !== context.repository ||
|
|
9
|
+
checkpoint.pr !== context.pr ||
|
|
10
|
+
checkpoint.triggerCommentId !== context.triggerCommentId ||
|
|
11
|
+
checkpoint.runId !== context.runId ||
|
|
12
|
+
checkpoint.headRef !== context.headRef ||
|
|
13
|
+
checkpoint.startSha !== context.headSha) {
|
|
14
|
+
throw new Error('Checkpoint comment does not match the active run binding');
|
|
15
|
+
}
|
|
16
|
+
if (status === 'running' && (!session.id || !session.url)) {
|
|
17
|
+
throw new Error('Running checkpoint requires a session id and URL');
|
|
18
|
+
}
|
|
19
|
+
if (session.url && (!URL.canParse(session.url) || !session.url.startsWith('https:'))) {
|
|
20
|
+
throw new Error('Session URL must be a parseable https URL');
|
|
21
|
+
}
|
|
22
|
+
const next = {
|
|
23
|
+
...checkpoint,
|
|
24
|
+
...(session.id ? { sessionId: session.id } : {}),
|
|
25
|
+
...(session.url ? { sessionUrl: session.url } : {}),
|
|
26
|
+
status,
|
|
27
|
+
updatedAt: new Date().toISOString(),
|
|
28
|
+
};
|
|
29
|
+
return renderCheckpoint(next);
|
|
30
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vouchington-tooling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Vouchington CLI and extractable tooling libraries.",
|
|
5
5
|
"homepage": "https://github.com/vouchington/vouchington-tooling/tree/main/packages/vouchington-tooling#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -14,11 +14,17 @@ Use when completed retrospectives or journals should become durable follow-up wo
|
|
|
14
14
|
3. Verify each candidate against the current base and search existing issues and open changes before
|
|
15
15
|
drafting. Skip work that is complete, explicitly rejected, or already covered.
|
|
16
16
|
4. Draft self-contained issues with the problem, concrete proposed work, relevant areas, and
|
|
17
|
-
|
|
17
|
+
validation. Route every authorized creation through
|
|
18
18
|
[github-issue](../github-issue/SKILL.md), including its repository gate, label approval, and
|
|
19
19
|
denied-external tracking behavior.
|
|
20
20
|
5. Archive only records that were fully processed under the repository's retention rules; report
|
|
21
21
|
created, updated, skipped, and deferred themes with reasons.
|
|
22
22
|
|
|
23
|
+
Use source records only for local verification and leave them in the repository's approved journal
|
|
24
|
+
or retention system. Public issue bodies contain only the minimum bounded facts or redacted summaries
|
|
25
|
+
needed to establish the problem, proposed work, relevant areas, and validation. Never embed
|
|
26
|
+
unredacted logs, command output, environment details, provider payloads, or transcript content.
|
|
27
|
+
|
|
23
28
|
This skill supplies no journal API, issue repository, labels, milestones, archival command, or
|
|
24
|
-
approval model.
|
|
29
|
+
approval model. Consumer wrappers cannot weaken this export boundary; they provide only those local
|
|
30
|
+
details.
|