vouchington-tooling 0.2.0 → 0.3.1

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.
Files changed (60) hide show
  1. package/README.md +36 -1
  2. package/dist/agent-blackboard/index.d.mts +41 -0
  3. package/dist/agent-blackboard/index.mjs +94 -0
  4. package/dist/agent-blackboard/session-id.d.mts +1 -0
  5. package/dist/agent-blackboard/session-id.mjs +5 -0
  6. package/dist/agent-blackboard/snapshot-cleanup-directory.d.mts +11 -0
  7. package/dist/agent-blackboard/snapshot-cleanup-directory.mjs +66 -0
  8. package/dist/agent-blackboard/snapshot-cleanup-key.d.mts +14 -0
  9. package/dist/agent-blackboard/snapshot-cleanup-key.mjs +129 -0
  10. package/dist/agent-blackboard/snapshot-cleanup-receipt.d.mts +6 -0
  11. package/dist/agent-blackboard/snapshot-cleanup-receipt.mjs +117 -0
  12. package/dist/agent-blackboard/snapshot-cleanup-resume.d.mts +16 -0
  13. package/dist/agent-blackboard/snapshot-cleanup-resume.mjs +98 -0
  14. package/dist/agent-blackboard/snapshot-partition-cleanup.d.mts +13 -0
  15. package/dist/agent-blackboard/snapshot-partition-cleanup.mjs +136 -0
  16. package/dist/agent-blackboard/snapshot-partition-format.d.mts +32 -0
  17. package/dist/agent-blackboard/snapshot-partition-format.mjs +151 -0
  18. package/dist/agent-blackboard/snapshot-partition-io.d.mts +4 -0
  19. package/dist/agent-blackboard/snapshot-partition-io.mjs +37 -0
  20. package/dist/agent-blackboard/snapshot-partition-read.d.mts +9 -0
  21. package/dist/agent-blackboard/snapshot-partition-read.mjs +70 -0
  22. package/dist/agent-blackboard/snapshot-partition-validate.d.mts +4 -0
  23. package/dist/agent-blackboard/snapshot-partition-validate.mjs +40 -0
  24. package/dist/agent-blackboard/snapshot-partition-write.d.mts +2 -0
  25. package/dist/agent-blackboard/snapshot-partition-write.mjs +91 -0
  26. package/dist/agent-blackboard/snapshot-partitions.d.mts +10 -0
  27. package/dist/agent-blackboard/snapshot-partitions.mjs +102 -0
  28. package/dist/agent-blackboard/snapshot-types.d.mts +67 -0
  29. package/dist/agent-blackboard/snapshot-types.mjs +1 -0
  30. package/dist/agent-blackboard/snapshot.d.mts +3 -0
  31. package/dist/agent-blackboard/snapshot.mjs +2 -0
  32. package/dist/cli/commands/agent-blackboard.d.mts +3 -0
  33. package/dist/cli/commands/agent-blackboard.mjs +114 -0
  34. package/dist/cli/commands/retrospective-facts.d.mts +1 -0
  35. package/dist/cli/commands/retrospective-facts.mjs +33 -0
  36. package/dist/cli/index.mjs +11 -0
  37. package/dist/cli/parse.d.mts +7 -1
  38. package/dist/cli/parse.mjs +6 -0
  39. package/dist/cli/usage.d.mts +1 -1
  40. package/dist/cli/usage.mjs +13 -1
  41. package/dist/gha-dependabot-automerge-action/index.test-helpers.d.mts +1 -1
  42. package/dist/gha-dependabot-automerge-action/index.test-helpers.mjs +2 -1
  43. package/dist/index.d.mts +4 -0
  44. package/dist/index.mjs +2 -0
  45. package/dist/retrospective-facts/exec.d.mts +3 -0
  46. package/dist/retrospective-facts/exec.mjs +19 -0
  47. package/dist/retrospective-facts/foreign.d.mts +2 -0
  48. package/dist/retrospective-facts/foreign.mjs +36 -0
  49. package/dist/retrospective-facts/format.d.mts +10 -0
  50. package/dist/retrospective-facts/format.mjs +64 -0
  51. package/dist/retrospective-facts/index.d.mts +3 -0
  52. package/dist/retrospective-facts/index.mjs +26 -0
  53. package/dist/retrospective-facts/local.d.mts +2 -0
  54. package/dist/retrospective-facts/local.mjs +144 -0
  55. package/dist/retrospective-facts/shared.d.mts +17 -0
  56. package/dist/retrospective-facts/shared.mjs +1 -0
  57. package/dist/skill-discovery/index.mjs +13 -1
  58. package/package.json +21 -2
  59. package/scripts/gha/harness-admission-lane.sh +26 -0
  60. package/scripts/gha/harness-assert-gates.sh +24 -0
@@ -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,2 @@
1
+ import { type CommandExecutor, type RetrospectiveFactsOptions } from './shared.mts';
2
+ export declare function foreignFacts(options: RetrospectiveFactsOptions, execute: CommandExecutor): Promise<string>;
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ import type { RetrospectiveFactsOptions } from './shared.mts';
2
+ export type { CommandExecutor, CommandResult, RetrospectiveFactsOptions } from './shared.mts';
3
+ export declare function runRetrospectiveFacts(options: RetrospectiveFactsOptions): Promise<string>;
@@ -0,0 +1,26 @@
1
+ import { shell } from './exec.mjs';
2
+ import { foreignFacts } from './foreign.mjs';
3
+ import { localFacts } from './local.mjs';
4
+ export async function runRetrospectiveFacts(options) {
5
+ validate(options);
6
+ const execute = options.execute ?? shell;
7
+ return options.repo ? foreignFacts(options, execute) : localFacts(options, execute);
8
+ }
9
+ function validate(options) {
10
+ if (options.pr !== undefined && !/^\d+$/.test(options.pr))
11
+ throw new Error('--pr requires a number');
12
+ if (options.branch !== undefined && (!options.branch || options.branch.startsWith('-')))
13
+ throw new Error('--branch requires a name');
14
+ if (options.repo !== undefined && !/^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/.test(options.repo))
15
+ throw new Error('--repo requires an owner/name value');
16
+ if (options.pr && options.noPr)
17
+ throw new Error('--pr and --no-pr are mutually exclusive');
18
+ if (options.repo && options.branch)
19
+ throw new Error('--branch cannot be combined with --repo');
20
+ if (options.repo && options.noPr)
21
+ throw new Error('--no-pr cannot be combined with --repo');
22
+ if (options.repo && !options.pr)
23
+ throw new Error('--repo requires --pr (a foreign repo has no current PR for this local branch)');
24
+ if (!options.pr && !options.branch && !options.noPr)
25
+ throw new Error('pass --pr <number>, --branch <name>, or --no-pr');
26
+ }
@@ -0,0 +1,2 @@
1
+ import { type CommandExecutor, type RetrospectiveFactsOptions } from './shared.mts';
2
+ export declare function localFacts(options: RetrospectiveFactsOptions, execute: CommandExecutor): Promise<string>;
@@ -0,0 +1,144 @@
1
+ import { rawBlock } from './exec.mjs';
2
+ import { apiFiles, count, dirs, format, objectField, readJson, stringField, topDirs, } from './format.mjs';
3
+ import { PR_JSON_FIELDS, } from './shared.mjs';
4
+ export async function localFacts(options, execute) {
5
+ const calls = [];
6
+ const run = async (command, args) => {
7
+ const result = await execute(command, args);
8
+ calls.push({ command, args, result });
9
+ return result;
10
+ };
11
+ const mainSpec = 'main:refs/remotes/origin/main';
12
+ const branchSpec = options.branch
13
+ ? `${options.branch}:refs/remotes/origin/${options.branch}`
14
+ : undefined;
15
+ const fetch = await run('git', ['fetch', 'origin', mainSpec]);
16
+ const branchFetch = branchSpec ? await run('git', ['fetch', 'origin', branchSpec]) : undefined;
17
+ const originMain = fetch.ok ||
18
+ (await run('git', ['rev-parse', '--verify', '--quiet', 'refs/remotes/origin/main'])).ok;
19
+ const branchResult = await run('git', ['branch', '--show-current']);
20
+ const localBranch = text(branchResult) || 'unavailable';
21
+ const selector = options.noPr ? undefined : (options.pr ?? options.branch);
22
+ const ghArgs = selector ? ['pr', 'view', selector, '--json', PR_JSON_FIELDS] : undefined;
23
+ const gh = ghArgs ? await run('gh', ghArgs) : undefined;
24
+ const data = gh?.ok ? readJson(gh.stdout) : undefined;
25
+ const state = options.noPr
26
+ ? 'none'
27
+ : stringField(data, 'state', gh?.ok ? 'unavailable' : gh ? 'gh failed' : 'unavailable');
28
+ const head = stringField(data, 'headRefName');
29
+ const branch = options.branch ??
30
+ (options.pr && head !== 'unavailable' ? head : options.noPr ? localBranch : 'unavailable');
31
+ const rangeName = options.branch ?? (options.noPr ? localBranch : undefined);
32
+ const localRange = options.noPr && options.branch === undefined && localBranch === 'unavailable'
33
+ ? 'HEAD'
34
+ : rangeName;
35
+ const resolved = localRange
36
+ ? await resolveNamedRef(localRange, run, localRange === options.branch && branchFetch?.ok === true)
37
+ : unresolvedRange();
38
+ const range = resolved.range;
39
+ const commitsResult = !data && range && originMain
40
+ ? await run('git', ['rev-list', '--count', `origin/main..${range}`])
41
+ : undefined;
42
+ const diffResult = !data && range && originMain
43
+ ? await run('git', ['diff', '--name-only', `origin/main...${range}`])
44
+ : undefined;
45
+ const scoped = Boolean(options.branch &&
46
+ (localBranch !== options.branch || (head !== 'unavailable' && head !== options.branch))) || Boolean(options.pr && !options.branch && (head === 'unavailable' || head !== localBranch));
47
+ const scope = options.branch ?? `#${options.pr}`;
48
+ const status = scoped
49
+ ? undefined
50
+ : await run('git', ['status', '--porcelain', '--untracked-files=normal']);
51
+ const reflog = scoped || branch === 'unavailable'
52
+ ? undefined
53
+ : await run('git', ['reflog', 'show', `origin/${branch}`]);
54
+ const merge = objectField(data, 'mergeCommit', 'oid');
55
+ const merged = await mergeFact(data, state, merge, resolved.range, originMain, options, run);
56
+ const filesText = diffResult?.ok ? text(diffResult) : undefined;
57
+ const raw = options.raw
58
+ ? `\n=== Raw Command Output ===\n${calls.map((call) => rawBlock(call.command, call.args, call.result)).join('')}`
59
+ : '';
60
+ return format({
61
+ fetch: `git fetch origin ${mainSpec}${branchSpec ? `; git fetch origin ${branchSpec}` : ''}`,
62
+ fetchStatus: fetch.ok ? 'ok' : 'failed',
63
+ fetchNote: `${fetch.ok
64
+ ? 'origin/main refreshed'
65
+ : originMain
66
+ ? 'using existing local origin/main ref after failed fetch'
67
+ : 'origin/main unavailable after failed fetch'}${resolved.refreshed ? `; origin/${localRange} refreshed` : ''}`,
68
+ branch,
69
+ pr: options.noPr ? 'none' : stringField(data, 'number'),
70
+ state,
71
+ mergedAt: stringField(data, 'mergedAt'),
72
+ mergeCommit: merge,
73
+ merged,
74
+ commits: data ? 'unavailable' : commitsResult?.ok ? text(commitsResult) : 'unavailable',
75
+ prCommits: data ? count(data, 'commits') : undefined,
76
+ files: data
77
+ ? apiFiles(data)
78
+ : filesText === undefined
79
+ ? 'unavailable'
80
+ : String(lines(filesText).length),
81
+ dirs: data ? dirs(data) : filesText === undefined ? 'unavailable' : topDirs(filesText),
82
+ changeSource: data ? 'api' : 'local',
83
+ remote: reflog === undefined
84
+ ? undefined
85
+ : reflog.ok
86
+ ? String(lines(text(reflog)).length)
87
+ : 'unavailable',
88
+ pushes: reflog === undefined
89
+ ? undefined
90
+ : reflog.ok
91
+ ? String(lines(text(reflog)).filter((line) => line.includes('update by push')).length)
92
+ : 'unavailable',
93
+ ...(scoped ? { scoped: scope } : {}),
94
+ working: status === undefined
95
+ ? undefined
96
+ : status.ok
97
+ ? String(lines(text(status)).length)
98
+ : 'unavailable',
99
+ }, raw);
100
+ }
101
+ function text(result) {
102
+ return result.stdout.trim();
103
+ }
104
+ function lines(value) {
105
+ return value.split('\n').filter(Boolean);
106
+ }
107
+ async function resolveNamedRef(name, run, fetched = false) {
108
+ if (name === 'HEAD')
109
+ return { range: name, refreshed: false };
110
+ if ((await run('git', ['rev-parse', '--verify', '--quiet', `refs/heads/${name}`])).ok)
111
+ return { range: name, refreshed: false };
112
+ const fetch = fetched
113
+ ? { ok: true, stdout: '', stderr: '' }
114
+ : await run('git', ['fetch', 'origin', `${name}:refs/remotes/origin/${name}`]);
115
+ if (!fetch.ok)
116
+ return { range: undefined, refreshed: false };
117
+ if ((await run('git', ['rev-parse', '--verify', '--quiet', `refs/remotes/origin/${name}`])).ok)
118
+ return { range: `origin/${name}`, refreshed: true };
119
+ return { range: undefined, refreshed: false };
120
+ }
121
+ function unresolvedRange() {
122
+ return { range: undefined, refreshed: false };
123
+ }
124
+ async function mergeFact(data, state, merge, range, originMain, options, run) {
125
+ if (merge && originMain) {
126
+ const inOrigin = await run('git', ['merge-base', '--is-ancestor', merge, 'origin/main']);
127
+ if (!inOrigin.ok)
128
+ return inOrigin.exitCode === 1 ? `no (origin/main lacks ${merge})` : 'unavailable';
129
+ const inLocalMain = await run('git', ['merge-base', '--is-ancestor', merge, 'main']);
130
+ if (!inLocalMain.ok && inLocalMain.exitCode === 1)
131
+ options.onWarning?.(`Warning: local main lacks PR merge commit ${merge}, but origin/main contains it.`);
132
+ return `yes (origin/main contains ${merge})`;
133
+ }
134
+ if (data && (state === 'OPEN' || state === 'CLOSED'))
135
+ return 'unmerged at time of retro';
136
+ if (!data && range && originMain) {
137
+ const rangeInOrigin = await run('git', ['merge-base', '--is-ancestor', range, 'origin/main']);
138
+ if (rangeInOrigin.ok)
139
+ return `yes (origin/main contains ${range})`;
140
+ if (rangeInOrigin.exitCode === 1)
141
+ return `no (origin/main lacks ${range})`;
142
+ }
143
+ return 'unavailable';
144
+ }
@@ -0,0 +1,17 @@
1
+ export declare const PR_JSON_FIELDS = "number,state,mergedAt,mergeCommit,changedFiles,files,commits,headRefName,baseRefName";
2
+ export type CommandResult = {
3
+ ok: boolean;
4
+ stdout: string;
5
+ stderr: string;
6
+ exitCode?: number | null;
7
+ };
8
+ export type CommandExecutor = (command: string, args: string[]) => Promise<CommandResult>;
9
+ export type RetrospectiveFactsOptions = {
10
+ pr?: string;
11
+ branch?: string;
12
+ noPr?: boolean;
13
+ repo?: string;
14
+ raw?: boolean;
15
+ execute?: CommandExecutor;
16
+ onWarning?: (message: string) => void;
17
+ };
@@ -0,0 +1 @@
1
+ export const PR_JSON_FIELDS = 'number,state,mergedAt,mergeCommit,changedFiles,files,commits,headRefName,baseRefName';
@@ -8,7 +8,16 @@ export async function linkSkill(options) {
8
8
  throw new Error(`Invalid skill name: ${options.name}`);
9
9
  const sourceRoot = resolve(options.sourceRoot);
10
10
  const canonicalSourceRoot = await realpath(sourceRoot);
11
- const manifest = await readSkillManifest(sourceRoot);
11
+ let manifest;
12
+ try {
13
+ manifest = await readSkillManifest(sourceRoot);
14
+ }
15
+ catch (error) {
16
+ if (!isMissingManifest(error))
17
+ throw error;
18
+ const targetRoot = await resolveTargetDirectory(options.targetRoot);
19
+ return linkResult(targetRoot, options.name, sourceRoot, canonicalSourceRoot, `${options.name}/SKILL.md`);
20
+ }
12
21
  const entry = manifest.skills.find((candidate) => candidate.name === options.name);
13
22
  if (entry === undefined)
14
23
  throw new Error(`Unknown skill: ${options.name}`);
@@ -17,6 +26,9 @@ export async function linkSkill(options) {
17
26
  const linking = new Set();
18
27
  return linkManifestSkill(sourceRoot, canonicalSourceRoot, targetRoot, manifest, entry, linked, linking);
19
28
  }
29
+ function isMissingManifest(error) {
30
+ return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';
31
+ }
20
32
  async function linkManifestSkill(sourceRoot, canonicalSourceRoot, targetRoot, manifest, entry, linked, linking) {
21
33
  if (linked.has(entry.name))
22
34
  return linkResult(targetRoot, entry.name, sourceRoot, canonicalSourceRoot, entry.path);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vouchington-tooling",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
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": {
@@ -46,6 +46,16 @@
46
46
  "import": "./dist/retrospective-transcript/index.mjs",
47
47
  "default": "./dist/retrospective-transcript/index.mjs"
48
48
  },
49
+ "./retrospective-facts": {
50
+ "types": "./dist/retrospective-facts/index.d.mts",
51
+ "import": "./dist/retrospective-facts/index.mjs",
52
+ "default": "./dist/retrospective-facts/index.mjs"
53
+ },
54
+ "./agent-blackboard": {
55
+ "types": "./dist/agent-blackboard/index.d.mts",
56
+ "import": "./dist/agent-blackboard/index.mjs",
57
+ "default": "./dist/agent-blackboard/index.mjs"
58
+ },
49
59
  "./session-friction": {
50
60
  "types": "./dist/session-friction/index.d.mts",
51
61
  "import": "./dist/session-friction/index.mjs",
@@ -239,7 +249,16 @@
239
249
  "yaml": "2.9.0"
240
250
  },
241
251
  "devDependencies": {
242
- "@types/picomatch": "^4.0.3"
252
+ "@types/picomatch": "^4.0.3",
253
+ "agent-blackboard": "^0.3.1"
254
+ },
255
+ "peerDependencies": {
256
+ "agent-blackboard": "^0.3.1"
257
+ },
258
+ "peerDependenciesMeta": {
259
+ "agent-blackboard": {
260
+ "optional": true
261
+ }
243
262
  },
244
263
  "optionalDependencies": {
245
264
  "@libpg-query/parser": "^18.0.0"
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ if [ "${#}" -ne 1 ]; then
5
+ echo "usage: harness-admission-lane.sh <lanes>" >&2
6
+ exit 2
7
+ fi
8
+
9
+ if [ -z "${GITHUB_OUTPUT:-}" ]; then
10
+ echo 'GITHUB_OUTPUT must be set' >&2
11
+ exit 2
12
+ fi
13
+
14
+ LANES="$1"
15
+ if ! [[ "$LANES" =~ ^[1-9][0-9]*$ ]]; then
16
+ echo "::error::admission lane count must be a positive integer, got: '$LANES'"
17
+ exit 1
18
+ fi
19
+
20
+ RUN_ID="${GITHUB_RUN_ID:-}"
21
+ if ! [[ "$RUN_ID" =~ ^[0-9]+$ ]]; then
22
+ echo "::error::GITHUB_RUN_ID must be a non-negative integer, got: '${RUN_ID}'"
23
+ exit 1
24
+ fi
25
+
26
+ echo "lane=$(( RUN_ID % LANES ))" >> "$GITHUB_OUTPUT"
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ if [ "$#" -eq 0 ]; then
5
+ echo "usage: harness-assert-gates.sh <gate>..." >&2
6
+ exit 2
7
+ fi
8
+
9
+ for gate in "$@"; do
10
+ if ! [[ "$gate" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
11
+ echo "::error::invalid gate name: '$gate'"
12
+ exit 1
13
+ fi
14
+ done
15
+
16
+ for gate in "$@"; do
17
+ value="${!gate:-}"
18
+ if [ "$value" = "true" ]; then
19
+ echo "::error::$gate must be disabled but is enabled"
20
+ exit 1
21
+ fi
22
+ done
23
+
24
+ exit 0