vouchington-tooling 0.2.0 → 0.3.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 +32 -0
- package/dist/agent-blackboard/index.d.mts +41 -0
- package/dist/agent-blackboard/index.mjs +94 -0
- package/dist/agent-blackboard/session-id.d.mts +1 -0
- package/dist/agent-blackboard/session-id.mjs +5 -0
- package/dist/agent-blackboard/snapshot-cleanup-directory.d.mts +11 -0
- package/dist/agent-blackboard/snapshot-cleanup-directory.mjs +66 -0
- package/dist/agent-blackboard/snapshot-cleanup-key.d.mts +14 -0
- package/dist/agent-blackboard/snapshot-cleanup-key.mjs +129 -0
- package/dist/agent-blackboard/snapshot-cleanup-receipt.d.mts +6 -0
- package/dist/agent-blackboard/snapshot-cleanup-receipt.mjs +117 -0
- package/dist/agent-blackboard/snapshot-cleanup-resume.d.mts +16 -0
- package/dist/agent-blackboard/snapshot-cleanup-resume.mjs +98 -0
- package/dist/agent-blackboard/snapshot-partition-cleanup.d.mts +13 -0
- package/dist/agent-blackboard/snapshot-partition-cleanup.mjs +136 -0
- package/dist/agent-blackboard/snapshot-partition-format.d.mts +32 -0
- package/dist/agent-blackboard/snapshot-partition-format.mjs +151 -0
- package/dist/agent-blackboard/snapshot-partition-io.d.mts +4 -0
- package/dist/agent-blackboard/snapshot-partition-io.mjs +37 -0
- package/dist/agent-blackboard/snapshot-partition-read.d.mts +9 -0
- package/dist/agent-blackboard/snapshot-partition-read.mjs +70 -0
- package/dist/agent-blackboard/snapshot-partition-validate.d.mts +4 -0
- package/dist/agent-blackboard/snapshot-partition-validate.mjs +40 -0
- package/dist/agent-blackboard/snapshot-partition-write.d.mts +2 -0
- package/dist/agent-blackboard/snapshot-partition-write.mjs +91 -0
- package/dist/agent-blackboard/snapshot-partitions.d.mts +10 -0
- package/dist/agent-blackboard/snapshot-partitions.mjs +102 -0
- package/dist/agent-blackboard/snapshot-types.d.mts +67 -0
- package/dist/agent-blackboard/snapshot-types.mjs +1 -0
- package/dist/agent-blackboard/snapshot.d.mts +3 -0
- package/dist/agent-blackboard/snapshot.mjs +2 -0
- package/dist/cli/commands/agent-blackboard.d.mts +3 -0
- package/dist/cli/commands/agent-blackboard.mjs +114 -0
- package/dist/cli/commands/retrospective-facts.d.mts +1 -0
- package/dist/cli/commands/retrospective-facts.mjs +33 -0
- package/dist/cli/index.mjs +11 -0
- package/dist/cli/parse.d.mts +7 -1
- package/dist/cli/parse.mjs +6 -0
- package/dist/cli/usage.d.mts +1 -1
- package/dist/cli/usage.mjs +12 -0
- package/dist/index.d.mts +4 -0
- package/dist/index.mjs +2 -0
- package/dist/retrospective-facts/exec.d.mts +3 -0
- package/dist/retrospective-facts/exec.mjs +19 -0
- package/dist/retrospective-facts/foreign.d.mts +2 -0
- package/dist/retrospective-facts/foreign.mjs +36 -0
- package/dist/retrospective-facts/format.d.mts +10 -0
- package/dist/retrospective-facts/format.mjs +64 -0
- package/dist/retrospective-facts/index.d.mts +3 -0
- package/dist/retrospective-facts/index.mjs +26 -0
- package/dist/retrospective-facts/local.d.mts +2 -0
- package/dist/retrospective-facts/local.mjs +144 -0
- package/dist/retrospective-facts/shared.d.mts +17 -0
- package/dist/retrospective-facts/shared.mjs +1 -0
- package/package.json +21 -2
- package/scripts/gha/harness-admission-lane.sh +26 -0
- package/scripts/gha/harness-assert-gates.sh +24 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { constants } from 'node:fs';
|
|
2
|
+
import { chmod, lstat, mkdtemp, open, rm } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { basename, dirname, isAbsolute, resolve } from 'node:path';
|
|
5
|
+
import { stageSnapshot } from './snapshot-partition-read.mjs';
|
|
6
|
+
import { writeCleanupReceipt } from './snapshot-cleanup-receipt.mjs';
|
|
7
|
+
import { readLines } from './snapshot-partition-io.mjs';
|
|
8
|
+
import { writePartitions } from './snapshot-partition-write.mjs';
|
|
9
|
+
const MAX_SESSIONS = 25;
|
|
10
|
+
const MAX_BYTES = 1024 * 1024;
|
|
11
|
+
const SOURCE_NAME = /^agent-blackboard-snapshot-[0-9a-f-]{36}\.jsonl$/;
|
|
12
|
+
const defaults = { open, mkdtemp, chmod };
|
|
13
|
+
let filesystem = defaults;
|
|
14
|
+
export function setSnapshotFilesystemForTest(overrides) {
|
|
15
|
+
filesystem = { ...defaults, ...overrides };
|
|
16
|
+
}
|
|
17
|
+
function assertLimit(value, fallback, label) {
|
|
18
|
+
const limit = value ?? fallback;
|
|
19
|
+
if (!Number.isSafeInteger(limit) || limit < 1)
|
|
20
|
+
throw new Error(`${label} must be a positive integer`);
|
|
21
|
+
return limit;
|
|
22
|
+
}
|
|
23
|
+
function assertGeneratedSnapshot(path) {
|
|
24
|
+
if (!isAbsolute(path))
|
|
25
|
+
throw new Error('snapshot path must be absolute');
|
|
26
|
+
if (dirname(resolve(path)) !== resolve(tmpdir()) || !SOURCE_NAME.test(basename(path)))
|
|
27
|
+
throw new Error('snapshot path must be a generated temporary snapshot path');
|
|
28
|
+
}
|
|
29
|
+
function assertVerification(bytes, checksum, manifest, options) {
|
|
30
|
+
if (options.checksum &&
|
|
31
|
+
(options.checksum.algorithm !== 'sha256' || options.checksum.value !== checksum))
|
|
32
|
+
throw new Error('snapshot checksum does not match');
|
|
33
|
+
if (options.counts &&
|
|
34
|
+
(options.counts.bytes !== bytes ||
|
|
35
|
+
options.counts.sessions !== manifest.counts.sessions ||
|
|
36
|
+
options.counts.entries !== manifest.counts.entries ||
|
|
37
|
+
options.counts.records !== manifest.counts.records))
|
|
38
|
+
throw new Error('snapshot counts do not match');
|
|
39
|
+
}
|
|
40
|
+
async function hashSnapshot(source) {
|
|
41
|
+
const hash = (await import('node:crypto')).createHash('sha256');
|
|
42
|
+
for await (const _line of readLines(source, hash)) {
|
|
43
|
+
// Consume the full descriptor to hash the same source a second time.
|
|
44
|
+
}
|
|
45
|
+
return hash.digest('hex');
|
|
46
|
+
}
|
|
47
|
+
export async function partitionSnapshot(options) {
|
|
48
|
+
assertGeneratedSnapshot(options.path);
|
|
49
|
+
const before = await lstat(options.path);
|
|
50
|
+
if (!before.isFile() || before.isSymbolicLink() || before.nlink !== 1)
|
|
51
|
+
throw new Error('snapshot path must be an unlinked generated regular file');
|
|
52
|
+
let source;
|
|
53
|
+
let stage;
|
|
54
|
+
let directory;
|
|
55
|
+
try {
|
|
56
|
+
source = await filesystem.open(options.path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
57
|
+
stage = await filesystem.mkdtemp(resolve(tmpdir(), 'agent-blackboard-partition-stage-'));
|
|
58
|
+
directory = await filesystem.mkdtemp(resolve(tmpdir(), 'agent-blackboard-partitions-'));
|
|
59
|
+
const permissions = await Promise.allSettled([
|
|
60
|
+
filesystem.chmod(stage, 0o700),
|
|
61
|
+
filesystem.chmod(directory, 0o700),
|
|
62
|
+
]);
|
|
63
|
+
const permissionFailure = permissions.find((result) => result.status === 'rejected');
|
|
64
|
+
if (permissionFailure?.status === 'rejected')
|
|
65
|
+
throw permissionFailure.reason;
|
|
66
|
+
const opened = await source.stat();
|
|
67
|
+
if (!opened.isFile() ||
|
|
68
|
+
opened.nlink !== 1 ||
|
|
69
|
+
opened.dev !== before.dev ||
|
|
70
|
+
opened.ino !== before.ino)
|
|
71
|
+
throw new Error('snapshot path changed while it was being opened');
|
|
72
|
+
const staged = await stageSnapshot(source, stage);
|
|
73
|
+
const after = await source.stat();
|
|
74
|
+
if (after.dev !== opened.dev ||
|
|
75
|
+
after.ino !== opened.ino ||
|
|
76
|
+
after.nlink !== 1 ||
|
|
77
|
+
opened.size !== staged.bytes ||
|
|
78
|
+
after.size !== staged.bytes)
|
|
79
|
+
throw new Error('snapshot path changed while it was being read');
|
|
80
|
+
if ((await hashSnapshot(source)) !== staged.checksum)
|
|
81
|
+
throw new Error('snapshot path changed while it was being read');
|
|
82
|
+
await source.close();
|
|
83
|
+
source = undefined;
|
|
84
|
+
assertVerification(staged.bytes, staged.checksum, staged.manifest, options);
|
|
85
|
+
const partitions = await writePartitions(staged.index, staged.manifest, directory, assertLimit(options.maxSessions, MAX_SESSIONS, 'maxSessions'), assertLimit(options.maxBytes, MAX_BYTES, 'maxBytes'));
|
|
86
|
+
return {
|
|
87
|
+
directory: directory,
|
|
88
|
+
partitions,
|
|
89
|
+
cleanupReceipt: await writeCleanupReceipt(directory, partitions),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
if (directory)
|
|
94
|
+
await rm(directory, { recursive: true, force: true });
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
await source?.close().catch(() => undefined);
|
|
99
|
+
if (stage)
|
|
100
|
+
await rm(stage, { recursive: true, force: true });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export type SnapshotSelection = {
|
|
2
|
+
agent?: string;
|
|
3
|
+
version?: string;
|
|
4
|
+
parentSessionId?: string | null;
|
|
5
|
+
data?: Record<string, unknown>;
|
|
6
|
+
inactiveForHours?: number;
|
|
7
|
+
};
|
|
8
|
+
export type SnapshotCounts = {
|
|
9
|
+
sessions: number;
|
|
10
|
+
entries: number;
|
|
11
|
+
records: number;
|
|
12
|
+
bytes: number;
|
|
13
|
+
};
|
|
14
|
+
export type SnapshotManifest = {
|
|
15
|
+
schemaVersion: 1;
|
|
16
|
+
status: 'complete';
|
|
17
|
+
createdAt: string;
|
|
18
|
+
completedAt: string;
|
|
19
|
+
selection: SnapshotSelection & {
|
|
20
|
+
archived: false;
|
|
21
|
+
};
|
|
22
|
+
counts: Omit<SnapshotCounts, 'bytes'>;
|
|
23
|
+
ordering: {
|
|
24
|
+
sessions: 'createdAt ascending';
|
|
25
|
+
entries: 'createdAt ascending within session';
|
|
26
|
+
};
|
|
27
|
+
consistency: 'best-effort';
|
|
28
|
+
};
|
|
29
|
+
export type SnapshotChecksum = {
|
|
30
|
+
algorithm: 'sha256';
|
|
31
|
+
value: string;
|
|
32
|
+
};
|
|
33
|
+
export type SnapshotPartitionOptions = {
|
|
34
|
+
path: string;
|
|
35
|
+
checksum?: SnapshotChecksum;
|
|
36
|
+
counts?: SnapshotCounts;
|
|
37
|
+
maxSessions?: number;
|
|
38
|
+
maxBytes?: number;
|
|
39
|
+
};
|
|
40
|
+
export type SnapshotPartition = {
|
|
41
|
+
path: string;
|
|
42
|
+
counts: SnapshotCounts;
|
|
43
|
+
checksum: SnapshotChecksum;
|
|
44
|
+
manifest: SnapshotManifest;
|
|
45
|
+
};
|
|
46
|
+
export type SnapshotCleanupReceipt = {
|
|
47
|
+
schemaVersion: 1;
|
|
48
|
+
directory: string;
|
|
49
|
+
directoryDev: number;
|
|
50
|
+
directoryIno: number;
|
|
51
|
+
token: string;
|
|
52
|
+
partitions: Array<{
|
|
53
|
+
name: string;
|
|
54
|
+
checksum: SnapshotChecksum;
|
|
55
|
+
}>;
|
|
56
|
+
signature: string;
|
|
57
|
+
};
|
|
58
|
+
export type SnapshotPartitionResult = {
|
|
59
|
+
directory: string;
|
|
60
|
+
partitions: SnapshotPartition[];
|
|
61
|
+
cleanupReceipt: SnapshotCleanupReceipt;
|
|
62
|
+
};
|
|
63
|
+
export type SnapshotCleanupOptions = {
|
|
64
|
+
path?: string;
|
|
65
|
+
directory?: string;
|
|
66
|
+
receipt?: SnapshotCleanupReceipt;
|
|
67
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { appendJournal, formatJournalEntries, probeBlackboard, readJournal, } from '../../agent-blackboard/index.mjs';
|
|
2
|
+
import { cleanupSnapshotPartitions, partitionSnapshot } from '../../agent-blackboard/snapshot.mjs';
|
|
3
|
+
let journalReader = readJournal;
|
|
4
|
+
export function setJournalReaderForTest(reader) {
|
|
5
|
+
journalReader = reader ?? readJournal;
|
|
6
|
+
}
|
|
7
|
+
export async function runAgentBlackboardCommand(args) {
|
|
8
|
+
try {
|
|
9
|
+
const [command, ...rest] = args;
|
|
10
|
+
if (command === 'probe' && rest.length === 0) {
|
|
11
|
+
await probeBlackboard();
|
|
12
|
+
return 0;
|
|
13
|
+
}
|
|
14
|
+
if (command === 'journal')
|
|
15
|
+
return await runJournal(rest);
|
|
16
|
+
if (command === 'snapshot')
|
|
17
|
+
return await runSnapshot(rest);
|
|
18
|
+
throw new Error('usage: agent-blackboard probe | journal append|entries | snapshot partition|cleanup');
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
22
|
+
return 2;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function runSnapshot(args) {
|
|
26
|
+
const [action, ...flags] = args;
|
|
27
|
+
const values = flagsToValues(flags);
|
|
28
|
+
if (action === 'cleanup') {
|
|
29
|
+
assertAllowed(values, ['snapshot', 'partition-directory', 'receipt']);
|
|
30
|
+
if (values['partition-directory'] && !values.receipt)
|
|
31
|
+
throw new Error('--receipt is required with --partition-directory');
|
|
32
|
+
await cleanupSnapshotPartitions({
|
|
33
|
+
...(values.snapshot ? { path: values.snapshot } : {}),
|
|
34
|
+
...(values['partition-directory'] ? { directory: values['partition-directory'] } : {}),
|
|
35
|
+
...(values.receipt ? { receipt: JSON.parse(values.receipt) } : {}),
|
|
36
|
+
});
|
|
37
|
+
process.stdout.write('{"cleaned":true}\n');
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
if (action === 'partition') {
|
|
41
|
+
assertAllowed(values, ['snapshot', 'checksum', 'counts']);
|
|
42
|
+
const counts = JSON.parse(required(values, 'counts'));
|
|
43
|
+
process.stdout.write(`${JSON.stringify(await partitionSnapshot({
|
|
44
|
+
path: required(values, 'snapshot'),
|
|
45
|
+
checksum: { algorithm: 'sha256', value: required(values, 'checksum') },
|
|
46
|
+
counts,
|
|
47
|
+
}))}\n`);
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
throw new Error('usage: agent-blackboard snapshot partition|cleanup');
|
|
51
|
+
}
|
|
52
|
+
async function runJournal(args) {
|
|
53
|
+
const [action, ...flags] = args;
|
|
54
|
+
const values = flagsToValues(flags);
|
|
55
|
+
if (action === 'entries') {
|
|
56
|
+
assertAllowed(values, ['session-id']);
|
|
57
|
+
const sessionId = required(values, 'session-id');
|
|
58
|
+
let entries;
|
|
59
|
+
try {
|
|
60
|
+
entries = await journalReader(sessionId);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (!isNotFound(error))
|
|
64
|
+
throw error;
|
|
65
|
+
entries = [];
|
|
66
|
+
}
|
|
67
|
+
process.stdout.write(`${formatJournalEntries(sessionId, entries)}\n`);
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
if (action === 'append') {
|
|
71
|
+
assertAllowed(values, [
|
|
72
|
+
'session-id',
|
|
73
|
+
'agent',
|
|
74
|
+
'version',
|
|
75
|
+
'file',
|
|
76
|
+
'parent-session-id',
|
|
77
|
+
'timestamp',
|
|
78
|
+
]);
|
|
79
|
+
process.stdout.write(`${await appendJournal({ sessionId: required(values, 'session-id'), agent: required(values, 'agent'), version: values.version ?? 'unknown', markdownFile: required(values, 'file'), ...(values['parent-session-id'] ? { parentSessionId: values['parent-session-id'] } : {}), ...('timestamp' in values ? { timestamp: values.timestamp } : {}) })}\n`);
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
throw new Error('usage: agent-blackboard journal append|entries');
|
|
83
|
+
}
|
|
84
|
+
function flagsToValues(flags) {
|
|
85
|
+
const values = {};
|
|
86
|
+
for (let index = 0; index < flags.length; index += 2) {
|
|
87
|
+
const flag = flags[index];
|
|
88
|
+
const value = flags[index + 1];
|
|
89
|
+
if (!flag?.startsWith('--') || value === undefined)
|
|
90
|
+
throw new Error(`invalid option: ${flag ?? ''}`);
|
|
91
|
+
const key = flag.slice(2);
|
|
92
|
+
if (key in values)
|
|
93
|
+
throw new Error(`duplicate option: ${flag}`);
|
|
94
|
+
values[key] = value;
|
|
95
|
+
}
|
|
96
|
+
return values;
|
|
97
|
+
}
|
|
98
|
+
function assertAllowed(values, allowed) {
|
|
99
|
+
for (const key of Object.keys(values))
|
|
100
|
+
if (!allowed.includes(key))
|
|
101
|
+
throw new Error(`unknown option: --${key}`);
|
|
102
|
+
}
|
|
103
|
+
function required(values, key) {
|
|
104
|
+
const value = values[key];
|
|
105
|
+
if (!value)
|
|
106
|
+
throw new Error(`--${key} is required`);
|
|
107
|
+
return value;
|
|
108
|
+
}
|
|
109
|
+
function isNotFound(error) {
|
|
110
|
+
return (typeof error === 'object' &&
|
|
111
|
+
error !== null &&
|
|
112
|
+
(('status' in error && error.status === 404) ||
|
|
113
|
+
('statusCode' in error && error.statusCode === 404)));
|
|
114
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runRetrospectiveFactsCommand(args: string[]): Promise<number>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util';
|
|
2
|
+
import { runRetrospectiveFacts, } from '../../retrospective-facts/index.mjs';
|
|
3
|
+
export async function runRetrospectiveFactsCommand(args) {
|
|
4
|
+
try {
|
|
5
|
+
const { values } = parseArgs({
|
|
6
|
+
args,
|
|
7
|
+
strict: true,
|
|
8
|
+
options: {
|
|
9
|
+
pr: { type: 'string' },
|
|
10
|
+
branch: { type: 'string' },
|
|
11
|
+
'no-pr': { type: 'boolean' },
|
|
12
|
+
repo: { type: 'string' },
|
|
13
|
+
raw: { type: 'boolean' },
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
const options = {
|
|
17
|
+
...(values.pr === undefined ? {} : { pr: values.pr }),
|
|
18
|
+
...(values.branch === undefined ? {} : { branch: values.branch }),
|
|
19
|
+
...(values['no-pr'] === undefined ? {} : { noPr: values['no-pr'] }),
|
|
20
|
+
...(values.repo === undefined ? {} : { repo: values.repo }),
|
|
21
|
+
...(values.raw ? { raw: true } : {}),
|
|
22
|
+
};
|
|
23
|
+
process.stdout.write(await runRetrospectiveFacts({
|
|
24
|
+
...options,
|
|
25
|
+
onWarning: (message) => process.stderr.write(`${message}\n`),
|
|
26
|
+
}));
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
31
|
+
return 2;
|
|
32
|
+
}
|
|
33
|
+
}
|
package/dist/cli/index.mjs
CHANGED
|
@@ -16,6 +16,8 @@ import { runSwiftSemanticEqualCommand } from './commands/swift-semantic-equal.mj
|
|
|
16
16
|
import { runVitestBlobManifestCommand } from './commands/vitest-blob-manifest.mjs';
|
|
17
17
|
import { runRetrospectiveTranscriptCommand } from './commands/retrospective-transcript.mjs';
|
|
18
18
|
import { runLinkSkill } from './commands/link-skill.mjs';
|
|
19
|
+
import { runRetrospectiveFactsCommand } from './commands/retrospective-facts.mjs';
|
|
20
|
+
import { runAgentBlackboardCommand } from './commands/agent-blackboard.mjs';
|
|
19
21
|
import { runWithHostLock } from './commands/with-host-lock.mjs';
|
|
20
22
|
import { parseCli } from './parse.mjs';
|
|
21
23
|
import { packageScriptPath } from './script-path.mjs';
|
|
@@ -58,6 +60,11 @@ const SCRIPT_PATHS = {
|
|
|
58
60
|
path: 'scripts/gha/install-playwright-chromium-arm64.sh',
|
|
59
61
|
},
|
|
60
62
|
'ghcr-package-retention': { command: 'bash', path: 'scripts/gha/ghcr-package-retention.sh' },
|
|
63
|
+
'harness-admission-lane': {
|
|
64
|
+
command: 'bash',
|
|
65
|
+
path: 'scripts/gha/harness-admission-lane.sh',
|
|
66
|
+
},
|
|
67
|
+
'harness-assert-gates': { command: 'bash', path: 'scripts/gha/harness-assert-gates.sh' },
|
|
61
68
|
};
|
|
62
69
|
export function runCli(argv = process.argv) {
|
|
63
70
|
const parsed = parseCli(argv);
|
|
@@ -102,6 +109,10 @@ export function runCli(argv = process.argv) {
|
|
|
102
109
|
return runRetrospectiveTranscriptCommand(parsed.args);
|
|
103
110
|
case 'link-skill':
|
|
104
111
|
return runLinkSkill(parsed);
|
|
112
|
+
case 'retrospective-facts':
|
|
113
|
+
return runRetrospectiveFactsCommand(parsed.args);
|
|
114
|
+
case 'agent-blackboard':
|
|
115
|
+
return runAgentBlackboardCommand(parsed.args);
|
|
105
116
|
}
|
|
106
117
|
}
|
|
107
118
|
function readInstalledVersion() {
|
package/dist/cli/parse.d.mts
CHANGED
|
@@ -48,6 +48,12 @@ export type ParsedCli = {
|
|
|
48
48
|
name: string;
|
|
49
49
|
sourceRoot: string;
|
|
50
50
|
targetRoot: string;
|
|
51
|
+
} | {
|
|
52
|
+
kind: 'retrospective-facts';
|
|
53
|
+
args: string[];
|
|
54
|
+
} | {
|
|
55
|
+
kind: 'agent-blackboard';
|
|
56
|
+
args: string[];
|
|
51
57
|
} | ParsedGhaRuntimeAudit | ParsedGhaArtifactsCleanup;
|
|
52
|
-
export type ScriptCommand = 'gha-output' | 'gha-needs-results' | 'download-with-diagnostics' | 'download-optional-run-artifacts' | 'host-pressure-diagnostics' | 'allocate-browser-safe-ports' | 'diagnose-port-collision' | 'prepare-trivy-db' | 'check-cache-size' | 'make-shard-matrix' | 'load-runner-env' | 'clean-workspace' | 'install-github-release' | 'run-with-timeout' | 'lint-links' | 'materialize-pr-context' | 'wait-for-apt-locks' | 'install-playwright-chromium-arm64' | 'ghcr-package-retention';
|
|
58
|
+
export type ScriptCommand = 'gha-output' | 'gha-needs-results' | 'download-with-diagnostics' | 'download-optional-run-artifacts' | 'host-pressure-diagnostics' | 'allocate-browser-safe-ports' | 'diagnose-port-collision' | 'prepare-trivy-db' | 'check-cache-size' | 'make-shard-matrix' | 'load-runner-env' | 'clean-workspace' | 'install-github-release' | 'run-with-timeout' | 'lint-links' | 'materialize-pr-context' | 'wait-for-apt-locks' | 'install-playwright-chromium-arm64' | 'ghcr-package-retention' | 'harness-admission-lane' | 'harness-assert-gates';
|
|
53
59
|
export declare function parseCli(argv: readonly string[]): ParsedCli;
|
package/dist/cli/parse.mjs
CHANGED
|
@@ -20,6 +20,8 @@ const SCRIPT_COMMANDS = new Set([
|
|
|
20
20
|
'wait-for-apt-locks',
|
|
21
21
|
'install-playwright-chromium-arm64',
|
|
22
22
|
'ghcr-package-retention',
|
|
23
|
+
'harness-admission-lane',
|
|
24
|
+
'harness-assert-gates',
|
|
23
25
|
]);
|
|
24
26
|
export function parseCli(argv) {
|
|
25
27
|
const args = argv.slice(2);
|
|
@@ -52,6 +54,10 @@ export function parseCli(argv) {
|
|
|
52
54
|
return { kind: 'retrospective-transcript', args: rest };
|
|
53
55
|
if (command === 'link-skill')
|
|
54
56
|
return parseLinkSkill(rest);
|
|
57
|
+
if (command === 'retrospective-facts')
|
|
58
|
+
return { kind: 'retrospective-facts', args: rest };
|
|
59
|
+
if (command === 'agent-blackboard')
|
|
60
|
+
return { kind: 'agent-blackboard', args: rest };
|
|
55
61
|
if (command === 'gha-artifacts-cleanup')
|
|
56
62
|
return parseGhaArtifactsCleanup(rest);
|
|
57
63
|
if (command !== undefined && SCRIPT_COMMANDS.has(command)) {
|
package/dist/cli/usage.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const USAGE = "Usage: vouchington <command> [options]\n\nCommands:\n runner-port-policy Print or validate a runner port policy\n with-host-lock Run a command under a host-wide lock\n gha-runtime-audit Audit successful GitHub Actions job runtimes\n gha-output Write a collision-safe multiline GITHUB_OUTPUT record\n gha-needs-results Fail if required GitHub Actions job results failed\n download-with-diagnostics Download a URL and report HTTP status on failure\n download-optional-run-artifacts Download optional artifacts from the current run\n host-pressure-diagnostics Print a bounded host memory/OOM/PSI snapshot\n allocate-browser-safe-ports Allocate Fetch-safe localhost ports\n diagnose-port-collision Capture bounded localhost port diagnostics\n prepare-trivy-db Download the Trivy vulnerability database\n gha-artifacts-cleanup Delete classified GitHub Actions artifacts\n http-origin Validate an optional HTTP(S) origin\n vitest-blob-manifest Stamp a vitest-blob-manifest:v1 identity file\n pnpm-install Install a pnpm workspace with retry and release-age fail-fast\n check-cache-size Measure a path and decide whether to save a GHA cache\n make-shard-matrix Emit a [1..N] GitHub Actions shard matrix\n load-runner-env Overlay a runner env file onto GITHUB_ENV with injection guards\n clean-workspace Reset a persistent-runner workspace with a fork-PR trust gate\n install-github-release Download a checksum-verified GitHub Release binary\n run-with-timeout Run a command with GNU timeout or a Perl fallback\n lint-links Two-pass lychee: internal links fail, external warn\n materialize-pr-context Dump PR title/body/files/diff/comments and #N crawl\n wait-for-apt-locks Wait until apt/dpkg lock files are free\n install-playwright-chromium-arm64 Install Playwright Chromium from browsers.json\n ghcr-package-retention Delete old GHCR package versions past KEEP_MIN\n nuget-central-version Validate a Directory.Packages.props PackageVersion delta\n swift-semantic-equal Compare Swift sources ignoring comments and whitespace\n post-review Post one COMMENT review from a staged payload file\n stage-review-payload Validate a review payload file into a staging directory\n retrospective-transcript Format facts from Claude-compatible, Codex, or Grok transcripts\n link-skill Link one packaged skill into an explicit consumer directory\n\nOptions:\n -h, --help Show this help\n -v, --version Print the package version\n\nrunner-port-policy\n (no args) Print the shipped policy as JSON\n --file <path> Validate and print a policy file\n --reserved <port> Print true if the port is reserved\n\nwith-host-lock\n --name <family>\n [--slots <n>]\n --timeout-seconds <n>\n [--command-timeout-seconds <n>]\n [--failure-diagnostics <absolute-script>]\n [--on-acquire-timeout fail|run-unlocked]\n -- <command> [args...]\n\ngha-runtime-audit\n [--repository owner/name] Default GITHUB_REPOSITORY\n [--branch main]\n --pr-workflow <name|/regex/> Repeatable\n --push-workflow <name|/regex/> Repeatable\n\ngha-output <name>\ngha-needs-results [label]\ndownload-with-diagnostics <url> <destination> [-- curl-args...]\ndownload-optional-run-artifacts (--name <name> | --pattern <pattern>) --dir <directory>\nhost-pressure-diagnostics\nallocate-browser-safe-ports [count] [--policy path] [--forbidden-ports path]\ndiagnose-port-collision [--ports \"2200 2216\"] [--output-dir PATH]\nprepare-trivy-db\ngha-artifacts-cleanup run --run-id <id> [--keep-pattern glob] [--delete-pattern glob] [--patterns-file json]\ngha-artifacts-cleanup sweep --older-than-hours <n> [--keep-pattern glob] [--delete-pattern glob] [--patterns-file json]\nhttp-origin [--field NAME] [value]\nvitest-blob-manifest <suite> [reports-directory]\npnpm-install --runner-lifecycle persistent|ephemeral|ephemeral-full --install-scripts true|false\ncheck-cache-size <path> <max-bytes> <label>\nmake-shard-matrix <total>\nload-runner-env\nclean-workspace\ninstall-github-release --repo owner/name --version X --asset 'name-{platform}.tar.gz' --bin name [--tag-prefix PREFIX] [--expected-sha256 SHA256] [--no-checksum] [--checksums-asset NAME] [--version-flag FLAG] [--bin-dir DIR]\nrun-with-timeout <timeout-seconds> <kill-after-seconds> <command...>\nlint-links [--offline] [--config PATH] [--glob PATTERN] [files...]\nmaterialize-pr-context\nwait-for-apt-locks\ninstall-playwright-chromium-arm64 [name:archive...]\nghcr-package-retention <url-encoded-package>...\nnuget-central-version <trusted-props> <candidate-props> <metadata-json> <output-props>\nswift-semantic-equal <base> <head> <file.swift>\npost-review\nstage-review-payload optional|required <source> <destination>\nretrospective-transcript [--session-id ID] [--jsonl PATH] [--projects-dir PATH] [--codex-sessions-dir PATH] [--grok-sessions-dir PATH]\nlink-skill <name> --source-root <skills-dir> --target-root <consumer-skills-dir>\n";
|
|
1
|
+
export declare const USAGE = "Usage: vouchington <command> [options]\n\nCommands:\n runner-port-policy Print or validate a runner port policy\n with-host-lock Run a command under a host-wide lock\n gha-runtime-audit Audit successful GitHub Actions job runtimes\n gha-output Write a collision-safe multiline GITHUB_OUTPUT record\n gha-needs-results Fail if required GitHub Actions job results failed\n download-with-diagnostics Download a URL and report HTTP status on failure\n download-optional-run-artifacts Download optional artifacts from the current run\n host-pressure-diagnostics Print a bounded host memory/OOM/PSI snapshot\n allocate-browser-safe-ports Allocate Fetch-safe localhost ports\n diagnose-port-collision Capture bounded localhost port diagnostics\n prepare-trivy-db Download the Trivy vulnerability database\n gha-artifacts-cleanup Delete classified GitHub Actions artifacts\n http-origin Validate an optional HTTP(S) origin\n vitest-blob-manifest Stamp a vitest-blob-manifest:v1 identity file\n pnpm-install Install a pnpm workspace with retry and release-age fail-fast\n check-cache-size Measure a path and decide whether to save a GHA cache\n make-shard-matrix Emit a [1..N] GitHub Actions shard matrix\n load-runner-env Overlay a runner env file onto GITHUB_ENV with injection guards\n clean-workspace Reset a persistent-runner workspace with a fork-PR trust gate\n install-github-release Download a checksum-verified GitHub Release binary\n run-with-timeout Run a command with GNU timeout or a Perl fallback\n lint-links Two-pass lychee: internal links fail, external warn\n materialize-pr-context Dump PR title/body/files/diff/comments and #N crawl\n wait-for-apt-locks Wait until apt/dpkg lock files are free\n install-playwright-chromium-arm64 Install Playwright Chromium from browsers.json\n ghcr-package-retention Delete old GHCR package versions past KEEP_MIN\n harness-admission-lane Compute a GITHUB_RUN_ID admission lane for fleet fan-out\n harness-assert-gates Fail if any named HARNESS_*_ENABLED gate is enabled\n nuget-central-version Validate a Directory.Packages.props PackageVersion delta\n swift-semantic-equal Compare Swift sources ignoring comments and whitespace\n post-review Post one COMMENT review from a staged payload file\n stage-review-payload Validate a review payload file into a staging directory\n retrospective-transcript Format facts from Claude-compatible, Codex, or Grok transcripts\n link-skill Link one packaged skill into an explicit consumer directory\n retrospective-facts Gather immutable facts for a retrospective\n agent-blackboard Probe and journal an Agent Blackboard deployment\n\nOptions:\n -h, --help Show this help\n -v, --version Print the package version\n\nrunner-port-policy\n (no args) Print the shipped policy as JSON\n --file <path> Validate and print a policy file\n --reserved <port> Print true if the port is reserved\n\nwith-host-lock\n --name <family>\n [--slots <n>]\n --timeout-seconds <n>\n [--command-timeout-seconds <n>]\n [--failure-diagnostics <absolute-script>]\n [--on-acquire-timeout fail|run-unlocked]\n -- <command> [args...]\n\ngha-runtime-audit\n [--repository owner/name] Default GITHUB_REPOSITORY\n [--branch main]\n --pr-workflow <name|/regex/> Repeatable\n --push-workflow <name|/regex/> Repeatable\n\ngha-output <name>\ngha-needs-results [label]\ndownload-with-diagnostics <url> <destination> [-- curl-args...]\ndownload-optional-run-artifacts (--name <name> | --pattern <pattern>) --dir <directory>\nhost-pressure-diagnostics\nallocate-browser-safe-ports [count] [--policy path] [--forbidden-ports path]\ndiagnose-port-collision [--ports \"2200 2216\"] [--output-dir PATH]\nprepare-trivy-db\ngha-artifacts-cleanup run --run-id <id> [--keep-pattern glob] [--delete-pattern glob] [--patterns-file json]\ngha-artifacts-cleanup sweep --older-than-hours <n> [--keep-pattern glob] [--delete-pattern glob] [--patterns-file json]\nhttp-origin [--field NAME] [value]\nvitest-blob-manifest <suite> [reports-directory]\npnpm-install --runner-lifecycle persistent|ephemeral|ephemeral-full --install-scripts true|false\ncheck-cache-size <path> <max-bytes> <label>\nmake-shard-matrix <total>\nload-runner-env\nclean-workspace\ninstall-github-release --repo owner/name --version X --asset 'name-{platform}.tar.gz' --bin name [--tag-prefix PREFIX] [--expected-sha256 SHA256] [--no-checksum] [--checksums-asset NAME] [--version-flag FLAG] [--bin-dir DIR]\nrun-with-timeout <timeout-seconds> <kill-after-seconds> <command...>\nlint-links [--offline] [--config PATH] [--glob PATTERN] [files...]\nmaterialize-pr-context\nwait-for-apt-locks\ninstall-playwright-chromium-arm64 [name:archive...]\nghcr-package-retention <url-encoded-package>...\nharness-admission-lane <lanes>\nharness-assert-gates <gate>...\nnuget-central-version <trusted-props> <candidate-props> <metadata-json> <output-props>\nswift-semantic-equal <base> <head> <file.swift>\npost-review\nstage-review-payload optional|required <source> <destination>\nretrospective-transcript [--session-id ID] [--jsonl PATH] [--projects-dir PATH] [--codex-sessions-dir PATH] [--grok-sessions-dir PATH]\nlink-skill <name> --source-root <skills-dir> --target-root <consumer-skills-dir>\nretrospective-facts (--pr NUMBER | --branch NAME | --no-pr) [--repo OWNER/NAME] [--raw]\nagent-blackboard probe\nagent-blackboard journal append --session-id UUID --agent NAME --version VERSION --file PATH [--parent-session-id UUID] [--timestamp ISO8601]\nagent-blackboard journal entries --session-id UUID\nagent-blackboard snapshot partition --snapshot PATH --checksum SHA256 --counts '{\"sessions\":N,\"entries\":N,\"records\":N,\"bytes\":N}'\nagent-blackboard snapshot cleanup [--snapshot PATH] [--partition-directory PATH --receipt JSON]\n";
|
|
2
2
|
export declare function printUsage(stream?: NodeJS.WritableStream): void;
|
package/dist/cli/usage.mjs
CHANGED
|
@@ -27,12 +27,16 @@ Commands:
|
|
|
27
27
|
wait-for-apt-locks Wait until apt/dpkg lock files are free
|
|
28
28
|
install-playwright-chromium-arm64 Install Playwright Chromium from browsers.json
|
|
29
29
|
ghcr-package-retention Delete old GHCR package versions past KEEP_MIN
|
|
30
|
+
harness-admission-lane Compute a GITHUB_RUN_ID admission lane for fleet fan-out
|
|
31
|
+
harness-assert-gates Fail if any named HARNESS_*_ENABLED gate is enabled
|
|
30
32
|
nuget-central-version Validate a Directory.Packages.props PackageVersion delta
|
|
31
33
|
swift-semantic-equal Compare Swift sources ignoring comments and whitespace
|
|
32
34
|
post-review Post one COMMENT review from a staged payload file
|
|
33
35
|
stage-review-payload Validate a review payload file into a staging directory
|
|
34
36
|
retrospective-transcript Format facts from Claude-compatible, Codex, or Grok transcripts
|
|
35
37
|
link-skill Link one packaged skill into an explicit consumer directory
|
|
38
|
+
retrospective-facts Gather immutable facts for a retrospective
|
|
39
|
+
agent-blackboard Probe and journal an Agent Blackboard deployment
|
|
36
40
|
|
|
37
41
|
Options:
|
|
38
42
|
-h, --help Show this help
|
|
@@ -82,12 +86,20 @@ materialize-pr-context
|
|
|
82
86
|
wait-for-apt-locks
|
|
83
87
|
install-playwright-chromium-arm64 [name:archive...]
|
|
84
88
|
ghcr-package-retention <url-encoded-package>...
|
|
89
|
+
harness-admission-lane <lanes>
|
|
90
|
+
harness-assert-gates <gate>...
|
|
85
91
|
nuget-central-version <trusted-props> <candidate-props> <metadata-json> <output-props>
|
|
86
92
|
swift-semantic-equal <base> <head> <file.swift>
|
|
87
93
|
post-review
|
|
88
94
|
stage-review-payload optional|required <source> <destination>
|
|
89
95
|
retrospective-transcript [--session-id ID] [--jsonl PATH] [--projects-dir PATH] [--codex-sessions-dir PATH] [--grok-sessions-dir PATH]
|
|
90
96
|
link-skill <name> --source-root <skills-dir> --target-root <consumer-skills-dir>
|
|
97
|
+
retrospective-facts (--pr NUMBER | --branch NAME | --no-pr) [--repo OWNER/NAME] [--raw]
|
|
98
|
+
agent-blackboard probe
|
|
99
|
+
agent-blackboard journal append --session-id UUID --agent NAME --version VERSION --file PATH [--parent-session-id UUID] [--timestamp ISO8601]
|
|
100
|
+
agent-blackboard journal entries --session-id UUID
|
|
101
|
+
agent-blackboard snapshot partition --snapshot PATH --checksum SHA256 --counts '{"sessions":N,"entries":N,"records":N,"bytes":N}'
|
|
102
|
+
agent-blackboard snapshot cleanup [--snapshot PATH] [--partition-directory PATH --receipt JSON]
|
|
91
103
|
`;
|
|
92
104
|
export function printUsage(stream = process.stdout) {
|
|
93
105
|
stream.write(USAGE);
|
package/dist/index.d.mts
CHANGED
|
@@ -2,6 +2,10 @@ export { linkSkill, readSkillManifest } from './skill-discovery/index.mts';
|
|
|
2
2
|
export type { LinkSkillOptions, LinkSkillResult, SkillManifest, SkillManifestEntry, } from './skill-discovery/index.mts';
|
|
3
3
|
export { codexChildren, codexIdentity, computeTranscriptFacts, formatTranscriptFacts, formatUnavailable, resolveTranscriptFile, runRetrospectiveTranscript, } from './retrospective-transcript/index.mts';
|
|
4
4
|
export type { ResolveOptions, TokenTotals, TranscriptFacts, } from './retrospective-transcript/index.mts';
|
|
5
|
+
export { runRetrospectiveFacts } from './retrospective-facts/index.mts';
|
|
6
|
+
export type { CommandExecutor, CommandResult, RetrospectiveFactsOptions, } from './retrospective-facts/index.mts';
|
|
7
|
+
export { appendJournal, assertSessionId, cleanupSnapshotPartitions, partitionSnapshot, probeBlackboard, readJournal, resolveBlackboardConnection, } from './agent-blackboard/index.mts';
|
|
8
|
+
export type { BlackboardConnection, SnapshotChecksum, SnapshotCleanupReceipt, SnapshotCleanupOptions, SnapshotCounts, SnapshotManifest, SnapshotPartition, SnapshotPartitionOptions, SnapshotPartitionResult, SnapshotSelection, } from './agent-blackboard/index.mts';
|
|
5
9
|
export { buildSessionFrictionReport, classifyFrictionObservation, FRICTION_LOG_MAX_EVENTS, isConformingCiFailureBlock, normalizeCommandPrefix, readFrictionLog, recordFriction, } from './session-friction/index.mts';
|
|
6
10
|
export type { FrictionEvent, FrictionEventKind, FrictionLogOptions, FrictionLogReadResult, FrictionObservation, JournalEntry, JournalLoader, JournalLoadResult, PermissionRequestObservation, SessionFrictionReport, SessionFrictionReportOptions, ToolResultObservation, } from './session-friction/index.mts';
|
|
7
11
|
export { EphemeralListenerAttemptsExhaustedError, isRunnerReservedPort, listenOnRunnerUnreservedEphemeralPort, loadRunnerPortPolicy, runnerPortPolicy, validateRunnerPortPolicy, } from './runner-port-policy/index.mts';
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/* eslint-disable max-lines -- package entry point enumerates the supported public API. */
|
|
2
2
|
export { linkSkill, readSkillManifest } from './skill-discovery/index.mjs';
|
|
3
3
|
export { codexChildren, codexIdentity, computeTranscriptFacts, formatTranscriptFacts, formatUnavailable, resolveTranscriptFile, runRetrospectiveTranscript, } from './retrospective-transcript/index.mjs';
|
|
4
|
+
export { runRetrospectiveFacts } from './retrospective-facts/index.mjs';
|
|
5
|
+
export { appendJournal, assertSessionId, cleanupSnapshotPartitions, partitionSnapshot, probeBlackboard, readJournal, resolveBlackboardConnection, } from './agent-blackboard/index.mjs';
|
|
4
6
|
export { buildSessionFrictionReport, classifyFrictionObservation, FRICTION_LOG_MAX_EVENTS, isConformingCiFailureBlock, normalizeCommandPrefix, readFrictionLog, recordFriction, } from './session-friction/index.mjs';
|
|
5
7
|
export { EphemeralListenerAttemptsExhaustedError, isRunnerReservedPort, listenOnRunnerUnreservedEphemeralPort, loadRunnerPortPolicy, runnerPortPolicy, validateRunnerPortPolicy, } from './runner-port-policy/index.mjs';
|
|
6
8
|
export { extractAlterTableAddColumnLocations, extractCreateIndexMetadata, extractCreateTableMetadata, extractDefaultFunction, extractDropIndexMetadata, extractFuncCallArgColumnNames, extractMigrationConstraintMetadata, initSqlAst, lineOfUtf8ByteOffset, MissingSqlAstParserError, parseSql, } from './sql-ast/index.mjs';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
export const shell = (command, args) => new Promise((resolve) => {
|
|
3
|
+
const child = spawn(command, args);
|
|
4
|
+
let stdout = '';
|
|
5
|
+
let stderr = '';
|
|
6
|
+
child.stdout.setEncoding('utf8');
|
|
7
|
+
child.stderr.setEncoding('utf8');
|
|
8
|
+
child.stdout.on('data', (data) => {
|
|
9
|
+
stdout += data;
|
|
10
|
+
});
|
|
11
|
+
child.stderr.on('data', (data) => {
|
|
12
|
+
stderr += data;
|
|
13
|
+
});
|
|
14
|
+
child.on('close', (exitCode) => resolve({ ok: exitCode === 0, stdout, stderr, exitCode }));
|
|
15
|
+
child.on('error', (error) => resolve({ ok: false, stdout, stderr: error.message, exitCode: null }));
|
|
16
|
+
});
|
|
17
|
+
export function rawBlock(command, args, result) {
|
|
18
|
+
return `$ ${command} ${args.join(' ')}\n${result.stdout}${result.stderr ? `${result.stdout ? '\n' : ''}stderr:\n${result.stderr}` : ''}\n\n`;
|
|
19
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { rawBlock } from './exec.mjs';
|
|
2
|
+
import { apiFiles, count, dirs, format, objectField, readJson, stringField } from './format.mjs';
|
|
3
|
+
import { PR_JSON_FIELDS } from './shared.mjs';
|
|
4
|
+
export async function foreignFacts(options, execute) {
|
|
5
|
+
const args = ['pr', 'view', options.pr, '--repo', options.repo, '--json', PR_JSON_FIELDS];
|
|
6
|
+
const result = await execute('gh', args);
|
|
7
|
+
const data = result.ok ? readJson(result.stdout) : undefined;
|
|
8
|
+
const state = stringField(data, 'state', result.ok ? 'unavailable' : 'gh failed');
|
|
9
|
+
const base = stringField(data, 'baseRefName');
|
|
10
|
+
const merged = state === 'MERGED'
|
|
11
|
+
? base === 'main'
|
|
12
|
+
? 'yes (GitHub reports PR MERGED into main)'
|
|
13
|
+
: base === 'unavailable'
|
|
14
|
+
? 'unavailable'
|
|
15
|
+
: `no (GitHub reports PR MERGED into ${base})`
|
|
16
|
+
: state === 'OPEN' || state === 'CLOSED'
|
|
17
|
+
? `no (GitHub reports PR ${state})`
|
|
18
|
+
: 'unavailable';
|
|
19
|
+
return format({
|
|
20
|
+
fetch: 'not run (scoped GitHub repository)',
|
|
21
|
+
fetchStatus: 'not run',
|
|
22
|
+
fetchNote: 'scoped GitHub state is authoritative',
|
|
23
|
+
branch: stringField(data, 'headRefName'),
|
|
24
|
+
pr: stringField(data, 'number'),
|
|
25
|
+
state,
|
|
26
|
+
mergedAt: stringField(data, 'mergedAt'),
|
|
27
|
+
mergeCommit: objectField(data, 'mergeCommit', 'oid'),
|
|
28
|
+
merged,
|
|
29
|
+
commits: 'unavailable',
|
|
30
|
+
prCommits: count(data, 'commits'),
|
|
31
|
+
files: apiFiles(data),
|
|
32
|
+
dirs: dirs(data),
|
|
33
|
+
changeSource: 'api',
|
|
34
|
+
scoped: `${options.repo}#${options.pr}`,
|
|
35
|
+
}, options.raw ? rawBlock('gh', args, result) : '');
|
|
36
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function format(f: Record<string, string | undefined> & {
|
|
2
|
+
changeSource?: 'api' | 'local';
|
|
3
|
+
}, raw: string): string;
|
|
4
|
+
export declare function readJson(value: string): Record<string, unknown> | undefined;
|
|
5
|
+
export declare function stringField(value: Record<string, unknown> | undefined, key: string, fallback?: string): string;
|
|
6
|
+
export declare function objectField(value: Record<string, unknown> | undefined, key: string, child: string): string | undefined;
|
|
7
|
+
export declare function count(value: Record<string, unknown> | undefined, key: string): string;
|
|
8
|
+
export declare function topDirs(files: string): string;
|
|
9
|
+
export declare function dirs(value: Record<string, unknown> | undefined): string;
|
|
10
|
+
export declare function apiFiles(value: Record<string, unknown> | undefined): string;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export function format(f, raw) {
|
|
2
|
+
const scoped = f.scoped ? `n/a (scoped to ${f.scoped})` : undefined;
|
|
3
|
+
const fileSource = f.changeSource === 'api' ? 'GitHub API' : 'origin/main';
|
|
4
|
+
return `=== Retrospective Facts ===\nFetch: ${f.fetch}\nFetch status: ${f.fetchStatus}\nFetch note: ${f.fetchNote}\nBranch: ${f.branch}\nPR: ${f.pr ?? 'unavailable'}\nPR state: ${f.state}\nPR merged at: ${f.mergedAt ?? 'unavailable'}\nPR merge commit: ${f.mergeCommit ?? 'unavailable'}\nMerged to main: ${f.merged}\nCommits ahead of origin/main: ${f.commits ?? 'unavailable'}\nPR commits: ${f.prCommits ?? 'unavailable'}\nRemote updates for origin/${f.branch}: ${f.remote ?? scoped ?? 'unavailable'}\nPush-like updates for origin/${f.branch}: ${f.pushes ?? scoped ?? 'unavailable'}\nFiles changed from ${fileSource}: ${f.files}\nTop-level dirs changed from ${fileSource}: ${f.dirs}\nWorking tree changes: ${scoped ?? f.working ?? 'unavailable'}\n${raw}`;
|
|
5
|
+
}
|
|
6
|
+
export function readJson(value) {
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(value);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return undefined;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function stringField(value, key, fallback = 'unavailable') {
|
|
15
|
+
const field = value?.[key];
|
|
16
|
+
return typeof field === 'string' || typeof field === 'number' ? String(field) : fallback;
|
|
17
|
+
}
|
|
18
|
+
export function objectField(value, key, child) {
|
|
19
|
+
const field = value?.[key];
|
|
20
|
+
return field && typeof field === 'object'
|
|
21
|
+
? stringField(field, child)
|
|
22
|
+
: undefined;
|
|
23
|
+
}
|
|
24
|
+
export function count(value, key) {
|
|
25
|
+
const length = listLength(value, key);
|
|
26
|
+
if (length === undefined)
|
|
27
|
+
return 'unavailable';
|
|
28
|
+
return length >= 100
|
|
29
|
+
? "100+ (gh's commits list caps at 100; actual count may be higher)"
|
|
30
|
+
: String(length);
|
|
31
|
+
}
|
|
32
|
+
function listLength(value, key) {
|
|
33
|
+
return Array.isArray(value?.[key]) ? value[key].length : undefined;
|
|
34
|
+
}
|
|
35
|
+
export function topDirs(files) {
|
|
36
|
+
const dirs = [
|
|
37
|
+
...new Set(files
|
|
38
|
+
.split('\n')
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.map((file) => (file.includes('/') ? file.split('/')[0] : 'root'))),
|
|
41
|
+
];
|
|
42
|
+
return dirs.length ? dirs.sort().join(',') : 'none';
|
|
43
|
+
}
|
|
44
|
+
export function dirs(value) {
|
|
45
|
+
const files = value?.files;
|
|
46
|
+
if (!Array.isArray(files))
|
|
47
|
+
return 'unavailable';
|
|
48
|
+
const result = topDirs(files
|
|
49
|
+
.map((file) => typeof file === 'object' && file
|
|
50
|
+
? stringField(file, 'path', '')
|
|
51
|
+
: '')
|
|
52
|
+
.join('\n'));
|
|
53
|
+
const total = stringField(value, 'changedFiles');
|
|
54
|
+
return total !== 'unavailable' && Number(total) > files.length
|
|
55
|
+
? `${result} (partial: gh returned ${files.length} of ${total} changed files)`
|
|
56
|
+
: result;
|
|
57
|
+
}
|
|
58
|
+
export function apiFiles(value) {
|
|
59
|
+
const total = stringField(value, 'changedFiles');
|
|
60
|
+
const listed = listLength(value, 'files');
|
|
61
|
+
return total !== 'unavailable' && listed !== undefined && Number(total) !== listed
|
|
62
|
+
? `${total} (partial: gh returned ${listed} of ${total} changed files)`
|
|
63
|
+
: total;
|
|
64
|
+
}
|