vouchington-tooling 0.22.1 → 0.24.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.
Files changed (76) hide show
  1. package/README.md +102 -4
  2. package/dist/agent-blackboard/client.d.mts +34 -0
  3. package/dist/agent-blackboard/client.mjs +41 -0
  4. package/dist/agent-blackboard/feedback-canonical.d.mts +1 -0
  5. package/dist/agent-blackboard/feedback-canonical.mjs +13 -0
  6. package/dist/agent-blackboard/feedback-codec.d.mts +7 -0
  7. package/dist/agent-blackboard/feedback-codec.mjs +121 -0
  8. package/dist/agent-blackboard/feedback-compose.d.mts +37 -0
  9. package/dist/agent-blackboard/feedback-compose.mjs +109 -0
  10. package/dist/agent-blackboard/feedback-deadline.d.mts +1 -0
  11. package/dist/agent-blackboard/feedback-deadline.mjs +18 -0
  12. package/dist/agent-blackboard/feedback-delivery.d.mts +18 -0
  13. package/dist/agent-blackboard/feedback-delivery.mjs +88 -0
  14. package/dist/agent-blackboard/feedback-fields.d.mts +4 -0
  15. package/dist/agent-blackboard/feedback-fields.mjs +38 -0
  16. package/dist/agent-blackboard/feedback-identity.d.mts +2 -0
  17. package/dist/agent-blackboard/feedback-identity.mjs +29 -0
  18. package/dist/agent-blackboard/feedback-online-error.d.mts +7 -0
  19. package/dist/agent-blackboard/feedback-online-error.mjs +23 -0
  20. package/dist/agent-blackboard/feedback-online.d.mts +2 -0
  21. package/dist/agent-blackboard/feedback-online.mjs +137 -0
  22. package/dist/agent-blackboard/feedback-outbox.d.mts +8 -0
  23. package/dist/agent-blackboard/feedback-outbox.mjs +157 -0
  24. package/dist/agent-blackboard/feedback-types.d.mts +66 -0
  25. package/dist/agent-blackboard/feedback-types.mjs +1 -0
  26. package/dist/agent-blackboard/index.d.mts +16 -39
  27. package/dist/agent-blackboard/index.mjs +35 -81
  28. package/dist/cli/commands/agent-blackboard.mjs +35 -4
  29. package/dist/cli/usage.d.mts +1 -1
  30. package/dist/cli/usage.mjs +3 -1
  31. package/dist/markdown/details-scope.d.mts +13 -0
  32. package/dist/markdown/details-scope.mjs +73 -0
  33. package/dist/markdown/document-sections.d.mts +3 -0
  34. package/dist/markdown/document-sections.mjs +64 -0
  35. package/dist/markdown/document-types.d.mts +21 -0
  36. package/dist/markdown/document-types.mjs +1 -0
  37. package/dist/markdown/index.d.mts +3 -0
  38. package/dist/markdown/index.mjs +2 -0
  39. package/dist/markdown/validate-sections.d.mts +3 -0
  40. package/dist/markdown/validate-sections.mjs +34 -0
  41. package/dist/retrospective-facts/coverage.d.mts +6 -0
  42. package/dist/retrospective-facts/coverage.mjs +42 -0
  43. package/dist/retrospective-facts/index.d.mts +3 -0
  44. package/dist/retrospective-facts/index.mjs +18 -1
  45. package/dist/retrospective-transcript/codex.mjs +8 -3
  46. package/dist/retrospective-transcript/index.d.mts +5 -0
  47. package/dist/retrospective-transcript/index.mjs +29 -9
  48. package/dist/session-friction/classify.mjs +25 -14
  49. package/dist/session-friction/coverage-ledger.d.mts +10 -0
  50. package/dist/session-friction/coverage-ledger.mjs +75 -0
  51. package/dist/session-friction/index.d.mts +1 -1
  52. package/dist/session-friction/log-file.d.mts +2 -2
  53. package/dist/session-friction/log-file.mjs +22 -10
  54. package/dist/session-friction/log.mjs +57 -10
  55. package/dist/session-friction/report.mjs +36 -7
  56. package/dist/session-friction/sandbox.mjs +11 -4
  57. package/dist/session-friction/types.d.mts +25 -4
  58. package/dist/workflow-policy/index.d.mts +1 -1
  59. package/dist/workflow-policy/index.mjs +1 -1
  60. package/dist/workflow-policy/permission-declarations.d.mts +14 -0
  61. package/dist/workflow-policy/permission-declarations.mjs +55 -0
  62. package/dist/workflow-policy/permission-inheritance.d.mts +8 -0
  63. package/dist/workflow-policy/permission-inheritance.mjs +88 -0
  64. package/dist/workflow-policy/permission-mismatches.d.mts +4 -1
  65. package/dist/workflow-policy/permission-mismatches.mjs +14 -3
  66. package/docs/workflow-and-compiler.md +18 -1
  67. package/package.json +1 -1
  68. package/skills/agent-workflow/SKILL.md +5 -0
  69. package/skills/agent-workflow/references/review.md +3 -0
  70. package/skills/blackboard/SKILL.md +34 -14
  71. package/skills/manifest.json +21 -21
  72. package/skills/pr-description/SKILL.md +44 -10
  73. package/skills/pr-description/references/examples.md +136 -0
  74. package/skills/retrospective/SKILL.md +27 -16
  75. package/skills/retrospective-distill/SKILL.md +15 -6
  76. package/skills/review-ci-logs/SKILL.md +10 -0
@@ -1,27 +1,10 @@
1
- var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
- if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
- return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
- return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
- });
6
- }
7
- return path;
8
- };
1
+ import { createFeedbackEnvelope, normalizeRepositories } from './feedback-codec.mjs';
2
+ import { writeFeedback } from './feedback-delivery.mjs';
3
+ import { loadClient, resolveBlackboardConnection, } from './client.mjs';
9
4
  import { readFile } from 'node:fs/promises';
10
- import { createRequire } from 'node:module';
11
- import { resolve } from 'node:path';
12
- import { pathToFileURL } from 'node:url';
13
5
  import { assertSessionId } from './session-id.mjs';
14
6
  export { cleanupSnapshotPartitions, partitionSnapshot } from './snapshot.mjs';
15
7
  export { assertSessionId } from './session-id.mjs';
16
- export function resolveBlackboardConnection(env = process.env) {
17
- const baseUrl = env.AGENT_BLACKBOARD_URL;
18
- const token = env.AGENT_BLACKBOARD_TOKEN;
19
- if (!baseUrl)
20
- throw new Error('AGENT_BLACKBOARD_URL is not set');
21
- if (!token)
22
- throw new Error('AGENT_BLACKBOARD_TOKEN is not set');
23
- return { baseUrl, token, readRetry: {} };
24
- }
25
8
  export async function probeBlackboard(env, dependencies) {
26
9
  const { Sessions } = await loadClient(dependencies);
27
10
  await new Sessions(resolveBlackboardConnection(env)).list({ limit: 1 });
@@ -30,7 +13,7 @@ export async function appendJournal(input) {
30
13
  assertSessionId(input.sessionId);
31
14
  if (input.parentSessionId != null)
32
15
  assertSessionId(input.parentSessionId, 'parent session id');
33
- const repositories = normalizeRepositories(input.repositories, true);
16
+ const repositories = normalizeRepositories(input.repositories);
34
17
  const timestamp = input.timestamp === undefined ? new Date() : new Date(input.timestamp);
35
18
  if (Number.isNaN(timestamp.valueOf()))
36
19
  throw new Error('journal timestamp is not a valid date-time');
@@ -38,48 +21,35 @@ export async function appendJournal(input) {
38
21
  try {
39
22
  markdown = new TextDecoder('utf-8', { fatal: true }).decode(await readFile(input.markdownFile));
40
23
  }
41
- catch (error) {
42
- throw new Error(`note file is not valid UTF-8: ${input.markdownFile}`, { cause: error });
24
+ catch {
25
+ throw new Error('note file is not valid UTF-8 or cannot be read');
43
26
  }
44
27
  if (!markdown)
45
- throw new Error(`note file is empty: ${input.markdownFile}`);
46
- const connection = resolveBlackboardConnection(input.env);
47
- const { Sessions, Entries } = await loadClient(input.dependencies);
48
- const sessions = new Sessions(connection);
49
- const ensured = await sessions.ensure({
50
- id: input.sessionId,
51
- parentSessionId: input.parentSessionId ?? null,
52
- agent: input.agent,
53
- version: input.version,
54
- });
55
- if (ensured.session.archivedAt != null)
56
- throw new Error(`session is archived; create a new session: ${input.sessionId}`);
57
- const current = ensured.session.data.repositories;
58
- const cumulative = normalizeRepositories(current, false);
59
- const merged = [...new Set([...cumulative, ...repositories])].sort();
60
- if (JSON.stringify(current) !== JSON.stringify(merged))
61
- await sessions.patch({ sessionId: input.sessionId, data: { repositories: merged } });
62
- const entry = await new Entries(connection).append({
63
- sessionId: input.sessionId,
64
- data: { type: 'journal', markdown, timestamp: timestamp.toISOString(), repositories },
28
+ throw new Error('note file is empty');
29
+ return writeFeedback({
30
+ identity: {
31
+ sessionId: input.sessionId,
32
+ parentSessionId: input.parentSessionId,
33
+ agent: input.agent,
34
+ version: input.version,
35
+ },
36
+ envelope: createFeedbackEnvelope({
37
+ schemaVersion: 1,
38
+ type: 'journal',
39
+ sourceEventId: input.sourceEventId,
40
+ timestamp: timestamp.toISOString(),
41
+ repositories,
42
+ markdown,
43
+ workOutcome: input.workOutcome,
44
+ feedbackCoverage: input.feedbackCoverage,
45
+ ...(input.category === undefined ? {} : { category: input.category }),
46
+ }),
47
+ mode: input.mode,
48
+ ...(input.outboxDirectory === undefined ? {} : { outboxDirectory: input.outboxDirectory }),
49
+ ...(input.env === undefined ? {} : { env: input.env }),
50
+ ...(input.dependencies === undefined ? {} : { dependencies: input.dependencies }),
51
+ ...(input.timeoutMs === undefined ? {} : { timeoutMs: input.timeoutMs }),
65
52
  });
66
- return `Journaled to agent-blackboard session ${input.sessionId} (entry created at ${entry.createdAt}).`;
67
- }
68
- function normalizeRepositories(value, required) {
69
- if (value === undefined && !required)
70
- return [];
71
- if (!Array.isArray(value) || (required && value.length === 0))
72
- throw new Error('repositories must be a non-empty array of owner/name strings');
73
- const repositories = [];
74
- for (const candidate of value) {
75
- if (typeof candidate !== 'string')
76
- throw new Error('repositories must be a non-empty array of owner/name strings');
77
- const repository = candidate.trim().toLowerCase();
78
- if (!/^[a-z0-9-]+\/[a-z0-9._-]+$/.test(repository))
79
- throw new Error(`invalid repository: ${candidate}`);
80
- repositories.push(repository);
81
- }
82
- return [...new Set(repositories)].sort();
83
53
  }
84
54
  export async function readJournal(sessionId, env, dependencies) {
85
55
  assertSessionId(sessionId);
@@ -115,24 +85,8 @@ export function formatJournalEntries(sessionId, entries) {
115
85
  .map(({ createdAt, markdown }) => `## ${createdAt}\n\n${markdown}`)
116
86
  .join('\n\n');
117
87
  }
118
- async function loadClient(dependencies = {}) {
119
- const loader = dependencies.loadClient ?? (() => defaultClientLoader(dependencies.resolveFrom));
120
- try {
121
- return await loader();
122
- }
123
- catch (error) {
124
- if (isMissingModuleError(error))
125
- throw new Error('agent-blackboard is not installed; install it alongside vouchington-tooling to use this integration', { cause: error });
126
- throw error;
127
- }
128
- }
129
- async function defaultClientLoader(resolveFrom) {
130
- const consumerRequire = createRequire(resolveFrom ?? resolve(process.cwd(), 'package.json'));
131
- const specifier = pathToFileURL(consumerRequire.resolve('agent-blackboard')).href;
132
- return (await import(__rewriteRelativeImportExtension(specifier)));
133
- }
134
- function isMissingModuleError(error) {
135
- return (error instanceof Error &&
136
- 'code' in error &&
137
- (error.code === 'ERR_MODULE_NOT_FOUND' || error.code === 'MODULE_NOT_FOUND'));
138
- }
88
+ export { resolveBlackboardConnection } from './client.mjs';
89
+ export { createFeedbackEnvelope, validateFeedbackEnvelope } from './feedback-codec.mjs';
90
+ export { writeFeedback, verifyFreshFeedback, flushFeedbackOutbox, FeedbackDeliveryError, } from './feedback-delivery.mjs';
91
+ export { feedbackOutboxStatus } from './feedback-outbox.mjs';
92
+ export { composeRetrospective } from './feedback-compose.mjs';
@@ -1,4 +1,4 @@
1
- import { appendJournal, formatJournalEntries, probeBlackboard, readJournal, } from '../../agent-blackboard/index.mjs';
1
+ import { appendJournal, flushFeedbackOutbox, formatJournalEntries, probeBlackboard, readJournal, } from '../../agent-blackboard/index.mjs';
2
2
  import { cleanupSnapshotPartitions, partitionSnapshot } from '../../agent-blackboard/snapshot.mjs';
3
3
  let journalReader = readJournal;
4
4
  export function setJournalReaderForTest(reader) {
@@ -15,7 +15,7 @@ export async function runAgentBlackboardCommand(args) {
15
15
  return await runJournal(rest);
16
16
  if (command === 'snapshot')
17
17
  return await runSnapshot(rest);
18
- throw new Error('usage: agent-blackboard probe | journal append|entries | snapshot partition|cleanup');
18
+ throw new Error('usage: agent-blackboard probe | journal append|entries|flush | snapshot partition|cleanup');
19
19
  }
20
20
  catch (error) {
21
21
  process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
@@ -53,6 +53,12 @@ async function runJournal(args) {
53
53
  const [action, ...flags] = args;
54
54
  const { repositories, remaining } = action === 'append' ? extractRepositories(flags) : { repositories: [], remaining: flags };
55
55
  const values = flagsToValues(remaining);
56
+ if (action === 'flush') {
57
+ assertAllowed(values, ['outbox-directory']);
58
+ const result = await flushFeedbackOutbox({ directory: required(values, 'outbox-directory') });
59
+ process.stdout.write(`${JSON.stringify(result)}\n`);
60
+ return 0;
61
+ }
56
62
  if (action === 'entries') {
57
63
  assertAllowed(values, ['session-id']);
58
64
  const sessionId = required(values, 'session-id');
@@ -76,11 +82,36 @@ async function runJournal(args) {
76
82
  'file',
77
83
  'parent-session-id',
78
84
  'timestamp',
85
+ 'mode',
86
+ 'source-event-id',
87
+ 'work-outcome',
88
+ 'coverage-status',
89
+ 'coverage-source',
90
+ 'dropped-count',
91
+ 'outbox-directory',
79
92
  ]);
80
- process.stdout.write(`${await appendJournal({ sessionId: required(values, 'session-id'), agent: required(values, 'agent'), version: values.version ?? 'unknown', repositories: requiredRepositories(repositories), markdownFile: required(values, 'file'), ...(values['parent-session-id'] ? { parentSessionId: values['parent-session-id'] } : {}), ...('timestamp' in values ? { timestamp: values.timestamp } : {}) })}\n`);
93
+ const result = await appendJournal({
94
+ mode: required(values, 'mode'),
95
+ sourceEventId: required(values, 'source-event-id'),
96
+ workOutcome: required(values, 'work-outcome'),
97
+ feedbackCoverage: {
98
+ status: required(values, 'coverage-status'),
99
+ sources: values['coverage-source']?.split(',') ?? [],
100
+ droppedCount: Number(values['dropped-count'] ?? '0'),
101
+ },
102
+ ...(values['outbox-directory'] ? { outboxDirectory: values['outbox-directory'] } : {}),
103
+ sessionId: required(values, 'session-id'),
104
+ agent: required(values, 'agent'),
105
+ version: values.version ?? 'unknown',
106
+ repositories: requiredRepositories(repositories),
107
+ markdownFile: required(values, 'file'),
108
+ ...('timestamp' in values ? { timestamp: values.timestamp } : {}),
109
+ parentSessionId: values['parent-session-id'] ?? null,
110
+ });
111
+ process.stdout.write(`${JSON.stringify(result)}\n`);
81
112
  return 0;
82
113
  }
83
- throw new Error('usage: agent-blackboard journal append|entries');
114
+ throw new Error('usage: agent-blackboard journal append|entries|flush');
84
115
  }
85
116
  function extractRepositories(flags) {
86
117
  const repositories = [];
@@ -1,4 +1,4 @@
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 agent-harness-config Apply classifier-auto + sandbox keys to agent harnesses\n gha-runtime-audit Audit successful GitHub Actions job runtimes\n require-up-to-date Require HEAD to include a fetched remote branch\n gitleaks-directory-scan Scan a directory with Gitleaks\n ast-grep-examples Run AST-grep rule examples\n ast-grep-pack Print shipped unconditional AST-grep pack paths as JSON\n gha-workspace-policy Check GitHub Actions workspace safety policy\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 vitest-report-attempt Write or read a Vitest report-attempt marker\n prepare-vitest-reports Validate and select Vitest report JSON files\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\nagent-harness-config dump\nagent-harness-config check|apply [--global] [--repo PATH]... [--harness claude|codex|grok|cursor]...\n [--home PATH]\n\ngha-runtime-audit\n [--repository owner/name] Default GITHUB_REPOSITORY\n [--branch main]\n [--median-threshold-floor <seconds>] Off unless set. Must be below the ceiling\n [--median-threshold-ceiling <seconds>] Default 360. Flags a five-sample median above this\n --pr-workflow <name|/regex/> Repeatable\n --push-workflow <name|/regex/> Repeatable\n\nrequire-up-to-date --remote <name> --branch <name>\ngitleaks-directory-scan --config <path> [--directory <path>]\nast-grep-examples --rules <directory> --config <path>\nast-grep-pack\ngha-workspace-policy [--root <directory>] [--workflow-directory <directory>] [--action-directory <directory>]\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]\nvitest-report-attempt <write DIRECTORY SUITE|read ROOT>\nprepare-vitest-reports [primary-directory] [fallback-directory] [output-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> Link a packaged or repository-local skill\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 --repository OWNER/NAME [--repository OWNER/NAME ...] [--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";
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 agent-harness-config Apply classifier-auto + sandbox keys to agent harnesses\n gha-runtime-audit Audit successful GitHub Actions job runtimes\n require-up-to-date Require HEAD to include a fetched remote branch\n gitleaks-directory-scan Scan a directory with Gitleaks\n ast-grep-examples Run AST-grep rule examples\n ast-grep-pack Print shipped unconditional AST-grep pack paths as JSON\n gha-workspace-policy Check GitHub Actions workspace safety policy\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 vitest-report-attempt Write or read a Vitest report-attempt marker\n prepare-vitest-reports Validate and select Vitest report JSON files\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\nagent-harness-config dump\nagent-harness-config check|apply [--global] [--repo PATH]... [--harness claude|codex|grok|cursor]...\n [--home PATH]\n\ngha-runtime-audit\n [--repository owner/name] Default GITHUB_REPOSITORY\n [--branch main]\n [--median-threshold-floor <seconds>] Off unless set. Must be below the ceiling\n [--median-threshold-ceiling <seconds>] Default 360. Flags a five-sample median above this\n --pr-workflow <name|/regex/> Repeatable\n --push-workflow <name|/regex/> Repeatable\n\nrequire-up-to-date --remote <name> --branch <name>\ngitleaks-directory-scan --config <path> [--directory <path>]\nast-grep-examples --rules <directory> --config <path>\nast-grep-pack\ngha-workspace-policy [--root <directory>] [--workflow-directory <directory>] [--action-directory <directory>]\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]\nvitest-report-attempt <write DIRECTORY SUITE|read ROOT>\nprepare-vitest-reports [primary-directory] [fallback-directory] [output-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> Link a packaged or repository-local skill\nretrospective-facts (--pr NUMBER | --branch NAME | --no-pr) [--repo OWNER/NAME] [--raw]\nagent-blackboard probe\nagent-blackboard journal append --mode interactive|autonomous --source-event-id ID --work-outcome OUTCOME --coverage-status STATUS --session-id UUID --agent NAME --version VERSION --file PATH --repository OWNER/NAME [--repository OWNER/NAME ...] [--parent-session-id UUID] [--timestamp ISO8601] [--coverage-source SOURCE,...] [--dropped-count N] [--outbox-directory PATH]\nagent-blackboard journal entries --session-id UUID\nagent-blackboard journal flush --outbox-directory PATH\nInteractive journal append requires --outbox-directory PATH.\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;
3
3
  export declare function commandNames(usage?: string): string[];
4
4
  export declare function commandUsage(command: string, usage?: string): string;
@@ -118,8 +118,10 @@ retrospective-transcript [--session-id ID] [--jsonl PATH] [--projects-dir PATH]
118
118
  link-skill <name> --source-root <skills-dir> --target-root <consumer-skills-dir> Link a packaged or repository-local skill
119
119
  retrospective-facts (--pr NUMBER | --branch NAME | --no-pr) [--repo OWNER/NAME] [--raw]
120
120
  agent-blackboard probe
121
- agent-blackboard journal append --session-id UUID --agent NAME --version VERSION --file PATH --repository OWNER/NAME [--repository OWNER/NAME ...] [--parent-session-id UUID] [--timestamp ISO8601]
121
+ agent-blackboard journal append --mode interactive|autonomous --source-event-id ID --work-outcome OUTCOME --coverage-status STATUS --session-id UUID --agent NAME --version VERSION --file PATH --repository OWNER/NAME [--repository OWNER/NAME ...] [--parent-session-id UUID] [--timestamp ISO8601] [--coverage-source SOURCE,...] [--dropped-count N] [--outbox-directory PATH]
122
122
  agent-blackboard journal entries --session-id UUID
123
+ agent-blackboard journal flush --outbox-directory PATH
124
+ Interactive journal append requires --outbox-directory PATH.
123
125
  agent-blackboard snapshot partition --snapshot PATH --checksum SHA256 --counts '{"sessions":N,"entries":N,"records":N,"bytes":N}'
124
126
  agent-blackboard snapshot cleanup [--snapshot PATH] [--partition-directory PATH --receipt JSON]
125
127
  `;
@@ -0,0 +1,13 @@
1
+ import type { MarkdownSectionsDiagnostic } from './document-types.mts';
2
+ /** Tracks disclosure tags from HTML nodes, never from fenced code or Markdown comments. */
3
+ export declare class DetailsScope {
4
+ private readonly markdown;
5
+ readonly diagnostics: MarkdownSectionsDiagnostic[];
6
+ private readonly frames;
7
+ constructor(markdown: string);
8
+ get visible(): boolean;
9
+ acceptHtml(html: string, startOffset: number): void;
10
+ finish(): void;
11
+ private acceptTag;
12
+ private problem;
13
+ }
@@ -0,0 +1,73 @@
1
+ /** Tracks disclosure tags from HTML nodes, never from fenced code or Markdown comments. */
2
+ export class DetailsScope {
3
+ markdown;
4
+ diagnostics = [];
5
+ frames = [];
6
+ constructor(markdown) {
7
+ this.markdown = markdown;
8
+ }
9
+ get visible() {
10
+ return this.frames.length === 0;
11
+ }
12
+ acceptHtml(html, startOffset) {
13
+ const tokens = /<!--[\s\S]*?(?:-->|$)|<\/?[a-z][a-z\d:-]*(?:[^"'<>]|"[^"]*"|'[^']*')*>/giu;
14
+ for (const token of html.matchAll(tokens)) {
15
+ if (token[0].startsWith('<!--'))
16
+ continue;
17
+ const tag = /^<(\/?)([a-z][a-z\d:-]*)/iu.exec(token[0]);
18
+ const name = tag[2].toLowerCase();
19
+ if (name !== 'details' && name !== 'summary')
20
+ continue;
21
+ const line = this.markdown.slice(0, startOffset + token.index).split('\n').length;
22
+ if (/\/\s*>$/u.test(token[0])) {
23
+ this.problem(line, `${tag[2]} cannot be self-closing`);
24
+ continue;
25
+ }
26
+ this.acceptTag(name, tag[1] === '/', line);
27
+ }
28
+ }
29
+ finish() {
30
+ for (const frame of this.frames)
31
+ this.problem(frame.line, 'details has no closing tag');
32
+ }
33
+ acceptTag(name, closing, line) {
34
+ if (name === 'details') {
35
+ if (!closing) {
36
+ if (this.frames.at(-1)?.summary === 'open') {
37
+ this.problem(line, 'details cannot open inside summary');
38
+ }
39
+ this.frames.push({ line, summary: 'absent' });
40
+ return;
41
+ }
42
+ const frame = this.frames.pop();
43
+ if (!frame)
44
+ this.problem(line, 'details closes without an opening tag');
45
+ else if (frame.summary === 'open')
46
+ this.problem(line, 'summary has no closing tag');
47
+ return;
48
+ }
49
+ const frame = this.frames.at(-1);
50
+ if (!frame) {
51
+ this.problem(line, 'summary must be inside details');
52
+ return;
53
+ }
54
+ if (closing) {
55
+ if (frame.summary !== 'open')
56
+ this.problem(line, 'summary closes without an opening tag');
57
+ else
58
+ frame.summary = 'closed';
59
+ }
60
+ else if (frame.summary !== 'absent') {
61
+ this.problem(line, 'details contains more than one summary or a nested summary');
62
+ }
63
+ else
64
+ frame.summary = 'open';
65
+ }
66
+ problem(line, reason) {
67
+ this.diagnostics.push({
68
+ code: 'malformed-details',
69
+ line,
70
+ message: `Malformed details: ${reason}.`,
71
+ });
72
+ }
73
+ }
@@ -0,0 +1,3 @@
1
+ import type { MarkdownSectionsDocument } from './document-types.mts';
2
+ /** Parses visible root H2 sections while retaining exact source ranges and disclosure scope. */
3
+ export declare function parseMarkdownSections(markdown: string): MarkdownSectionsDocument;
@@ -0,0 +1,64 @@
1
+ import { parseGfmMarkdown } from './ast.mjs';
2
+ import { DetailsScope } from './details-scope.mjs';
3
+ /** Parses visible root H2 sections while retaining exact source ranges and disclosure scope. */
4
+ export function parseMarkdownSections(markdown) {
5
+ const sections = [];
6
+ const scope = new DetailsScope(markdown);
7
+ let current;
8
+ const visit = (node, collectText) => {
9
+ if (node.type === 'html') {
10
+ scope.acceptHtml(node.value, node.position.start.offset);
11
+ return;
12
+ }
13
+ if (node.type === 'code' || node.type === 'image')
14
+ return;
15
+ if (collectText &&
16
+ scope.visible &&
17
+ current &&
18
+ (node.type === 'text' || node.type === 'inlineCode') &&
19
+ node.value.trim()) {
20
+ current.hasVisibleContent = true;
21
+ }
22
+ if (!('children' in node))
23
+ return;
24
+ for (const [index, child] of node.children.entries()) {
25
+ visit(child, collectText && node.type !== 'heading' && (node.type !== 'table' || index > 0));
26
+ }
27
+ };
28
+ for (const node of parseGfmMarkdown(markdown).children) {
29
+ if (node.type === 'heading') {
30
+ const wasVisible = scope.visible;
31
+ const heading = headingText(node, scope).replace(/\s+/gu, ' ').trim();
32
+ if (node.depth === 2 && wasVisible && heading) {
33
+ if (current)
34
+ current.endOffset = node.position.start.offset;
35
+ current = {
36
+ heading,
37
+ content: '',
38
+ startOffset: node.position.end.offset,
39
+ endOffset: markdown.length,
40
+ line: node.position.start.line,
41
+ hasVisibleContent: false,
42
+ };
43
+ sections.push(current);
44
+ }
45
+ continue;
46
+ }
47
+ visit(node, node.type === 'paragraph' || node.type === 'list' || node.type === 'table');
48
+ }
49
+ scope.finish();
50
+ for (const section of sections)
51
+ section.content = markdown.slice(section.startOffset, section.endOffset);
52
+ return { sections, diagnostics: scope.diagnostics };
53
+ }
54
+ function headingText(node, scope) {
55
+ if (node.type === 'html') {
56
+ scope.acceptHtml(node.value, node.position.start.offset);
57
+ return '';
58
+ }
59
+ if (node.type === 'text' || node.type === 'inlineCode')
60
+ return scope.visible ? node.value : '';
61
+ if (!('children' in node))
62
+ return '';
63
+ return node.children.map((child) => headingText(child, scope)).join('');
64
+ }
@@ -0,0 +1,21 @@
1
+ export type MarkdownVisibleSection = {
2
+ heading: string;
3
+ content: string;
4
+ startOffset: number;
5
+ endOffset: number;
6
+ line: number;
7
+ hasVisibleContent: boolean;
8
+ };
9
+ export type MarkdownSectionsDiagnostic = {
10
+ code: 'missing-heading' | 'duplicate-heading' | 'empty-section' | 'malformed-details';
11
+ message: string;
12
+ heading?: string;
13
+ line?: number;
14
+ };
15
+ export type MarkdownSectionsDocument = {
16
+ sections: MarkdownVisibleSection[];
17
+ diagnostics: MarkdownSectionsDiagnostic[];
18
+ };
19
+ export type ValidateMarkdownSectionsOptions = {
20
+ requiredHeadings: readonly string[];
21
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,8 @@
1
1
  export { findMarkdownNode, findMarkdownNodes, parseGfmMarkdown, walkMarkdown } from './ast.mts';
2
2
  export { extractLooseMarkdownTableRows } from './loose-pipe-rows.mts';
3
+ export { parseMarkdownSections } from './document-sections.mts';
4
+ export { validateMarkdownSections } from './validate-sections.mts';
5
+ export type { MarkdownVisibleSection, MarkdownSectionsDiagnostic, MarkdownSectionsDocument, ValidateMarkdownSectionsOptions, } from './document-types.mts';
3
6
  export { markdownSectionBetweenHeadings } from './sections.mts';
4
7
  export { extractMarkdownTables, parseMarkdownTables } from './tables.mts';
5
8
  export { markdownNodeText } from './text.mts';
@@ -1,5 +1,7 @@
1
1
  export { findMarkdownNode, findMarkdownNodes, parseGfmMarkdown, walkMarkdown } from './ast.mjs';
2
2
  export { extractLooseMarkdownTableRows } from './loose-pipe-rows.mjs';
3
+ export { parseMarkdownSections } from './document-sections.mjs';
4
+ export { validateMarkdownSections } from './validate-sections.mjs';
3
5
  export { markdownSectionBetweenHeadings } from './sections.mjs';
4
6
  export { extractMarkdownTables, parseMarkdownTables } from './tables.mjs';
5
7
  export { markdownNodeText } from './text.mjs';
@@ -0,0 +1,3 @@
1
+ import type { MarkdownSectionsDiagnostic, MarkdownSectionsDocument, ValidateMarkdownSectionsOptions } from './document-types.mts';
2
+ /** Validates caller-owned headings and visible core content without inferring semantic relevance. */
3
+ export declare function validateMarkdownSections(document: MarkdownSectionsDocument, options: ValidateMarkdownSectionsOptions): MarkdownSectionsDiagnostic[];
@@ -0,0 +1,34 @@
1
+ /** Validates caller-owned headings and visible core content without inferring semantic relevance. */
2
+ export function validateMarkdownSections(document, options) {
3
+ const diagnostics = [...document.diagnostics];
4
+ for (const heading of options.requiredHeadings) {
5
+ const sections = document.sections.filter((section) => section.heading === heading);
6
+ if (sections.length === 0) {
7
+ diagnostics.push({
8
+ code: 'missing-heading',
9
+ heading,
10
+ message: `Missing required section: ${heading}.`,
11
+ });
12
+ continue;
13
+ }
14
+ if (sections.length > 1) {
15
+ diagnostics.push({
16
+ code: 'duplicate-heading',
17
+ heading,
18
+ line: sections[1].line,
19
+ message: `Duplicate required section: ${heading}.`,
20
+ });
21
+ }
22
+ for (const section of sections) {
23
+ if (!section.hasVisibleContent) {
24
+ diagnostics.push({
25
+ code: 'empty-section',
26
+ heading,
27
+ line: section.line,
28
+ message: `Required section has no visible prose, list, or table content: ${heading}.`,
29
+ });
30
+ }
31
+ }
32
+ }
33
+ return diagnostics;
34
+ }
@@ -0,0 +1,6 @@
1
+ import type { CommandResult } from './shared.mts';
2
+ export type RetrospectiveFactsReport = {
3
+ markdown: string;
4
+ coverage: 'complete' | 'partial' | 'unavailable';
5
+ };
6
+ export declare function completeCommandFacts(command: string, args: string[], result: CommandResult): boolean;
@@ -0,0 +1,42 @@
1
+ import { readJson } from './format.mjs';
2
+ export function completeCommandFacts(command, args, result) {
3
+ if (command === 'git' && args[0] === 'merge-base' && args[1] === '--is-ancestor')
4
+ return result.ok || result.exitCode === 1;
5
+ if (!result.ok)
6
+ return false;
7
+ if (command !== 'gh') {
8
+ if (args[0] === 'branch')
9
+ return Boolean(result.stdout.trim());
10
+ if (args[0] === 'rev-list')
11
+ return /^\d+$/.test(result.stdout.trim());
12
+ return true;
13
+ }
14
+ const data = readJson(result.stdout);
15
+ if (!data || typeof data !== 'object' || Array.isArray(data))
16
+ return false;
17
+ if (!Number.isInteger(data.number) ||
18
+ Number(data.number) < 1 ||
19
+ typeof data.state !== 'string' ||
20
+ !['OPEN', 'CLOSED', 'MERGED'].includes(data.state) ||
21
+ typeof data.headRefName !== 'string' ||
22
+ !data.headRefName ||
23
+ typeof data.baseRefName !== 'string' ||
24
+ !data.baseRefName ||
25
+ !Number.isInteger(data.changedFiles) ||
26
+ Number(data.changedFiles) < 0 ||
27
+ !Array.isArray(data.files) ||
28
+ data.files.length !== data.changedFiles ||
29
+ !data.files.every((file) => file && typeof file === 'object' && typeof file.path === 'string' && file.path) ||
30
+ !Array.isArray(data.commits) ||
31
+ data.commits.length >= 100)
32
+ return false;
33
+ if (data.state === 'MERGED')
34
+ return (typeof data.mergedAt === 'string' &&
35
+ Boolean(data.mergedAt) &&
36
+ Boolean(data.mergeCommit &&
37
+ typeof data.mergeCommit === 'object' &&
38
+ 'oid' in data.mergeCommit &&
39
+ typeof data.mergeCommit.oid === 'string' &&
40
+ data.mergeCommit.oid));
41
+ return true;
42
+ }
@@ -1,3 +1,6 @@
1
+ import { type RetrospectiveFactsReport } from './coverage.mts';
1
2
  import type { RetrospectiveFactsOptions } from './shared.mts';
2
3
  export type { CommandExecutor, CommandResult, RetrospectiveFactsOptions } from './shared.mts';
4
+ export type { RetrospectiveFactsReport } from './coverage.mts';
3
5
  export declare function runRetrospectiveFacts(options: RetrospectiveFactsOptions): Promise<string>;
6
+ export declare function runRetrospectiveFactsReport(options: RetrospectiveFactsOptions): Promise<RetrospectiveFactsReport>;
@@ -1,10 +1,27 @@
1
1
  import { shell } from './exec.mjs';
2
2
  import { foreignFacts } from './foreign.mjs';
3
3
  import { localFacts } from './local.mjs';
4
+ import { completeCommandFacts } from './coverage.mjs';
4
5
  export async function runRetrospectiveFacts(options) {
6
+ return (await runRetrospectiveFactsReport(options)).markdown;
7
+ }
8
+ export async function runRetrospectiveFactsReport(options) {
5
9
  validate(options);
6
10
  const execute = options.execute ?? shell;
7
- return options.repo ? foreignFacts(options, execute) : localFacts(options, execute);
11
+ let complete = true;
12
+ let successful = 0;
13
+ const collect = async (command, args) => {
14
+ const result = await execute(command, args);
15
+ if (result.ok)
16
+ successful++;
17
+ if (!completeCommandFacts(command, args, result))
18
+ complete = false;
19
+ return result;
20
+ };
21
+ const markdown = await (options.repo
22
+ ? foreignFacts(options, collect)
23
+ : localFacts(options, collect));
24
+ return { markdown, coverage: complete ? 'complete' : successful ? 'partial' : 'unavailable' };
8
25
  }
9
26
  function validate(options) {
10
27
  if (options.pr !== undefined && !/^\d+$/.test(options.pr))
@@ -5,10 +5,15 @@ export function codexChildren(lines, ownerPath = '/root') {
5
5
  const base = ownerPath.replace(/\/$/, '');
6
6
  for (const record of parseLines(lines)) {
7
7
  const payload = asRecord(record.payload);
8
- if (record.type !== 'event_msg' || payload?.type !== 'sub_agent_activity')
8
+ if (record.type !== 'event_msg')
9
9
  continue;
10
- const threadId = payload.agent_thread_id;
11
- const agentPath = payload.agent_path;
10
+ const activity = payload?.type === 'sub_agent_activity'
11
+ ? payload
12
+ : payload?.type === 'item_completed' && asRecord(payload.item)?.type === 'SubAgentActivity'
13
+ ? asRecord(payload.item)
14
+ : undefined;
15
+ const threadId = activity?.agent_thread_id;
16
+ const agentPath = activity?.agent_path;
12
17
  if (typeof threadId !== 'string' || typeof agentPath !== 'string')
13
18
  continue;
14
19
  const normalized = agentPath.replace(/\/$/, '');
@@ -5,5 +5,10 @@ export { codexChildren, codexIdentity } from './codex.mts';
5
5
  export { formatTranscriptFacts, formatUnavailable } from './format.mts';
6
6
  export type { ResolveOptions } from './resolve-transcript-file.mts';
7
7
  export { resolveTranscriptFile } from './resolve-transcript-file.mts';
8
+ export type RetrospectiveTranscriptReport = {
9
+ markdown: string;
10
+ coverage: 'complete' | 'partial' | 'unavailable';
11
+ };
8
12
  export declare function computeTranscriptFacts(lines: string[], subagents?: Array<string[] | CodexSegment>): TranscriptFacts;
9
13
  export declare function runRetrospectiveTranscript(options: ResolveOptions): Promise<string>;
14
+ export declare function runRetrospectiveTranscriptReport(options: ResolveOptions): Promise<RetrospectiveTranscriptReport>;