vouchington-tooling 0.18.0 → 0.19.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 +18 -3
- package/dist/agent-blackboard/index.d.mts +6 -0
- package/dist/agent-blackboard/index.mjs +27 -2
- package/dist/agent-blackboard/snapshot-partition-format.mjs +5 -0
- package/dist/agent-blackboard/snapshot-types.d.mts +1 -0
- package/dist/cli/commands/agent-blackboard.mjs +23 -2
- package/dist/cli/usage.d.mts +1 -1
- package/dist/cli/usage.mjs +2 -2
- package/dist/gha-download-optional-run-artifacts.test-helpers.d.mts +17 -0
- package/dist/gha-download-optional-run-artifacts.test-helpers.mjs +50 -0
- package/package.json +3 -3
- package/scripts/gha/download-optional-run-artifacts.sh +63 -24
- package/scripts/gha/write-github-multiline-output.sh +6 -6
- package/skills/blackboard/SKILL.md +3 -0
- package/skills/retrospective/SKILL.md +2 -0
- package/skills/retrospective-distill/SKILL.md +6 -2
- package/skills/test-authoring/SKILL.md +3 -0
- package/skills/test-authoring/references/core-practice.md +3 -1
- package/skills/test-authoring/references/dependency-boundaries.md +37 -0
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ npm install @libpg-query/parser
|
|
|
10
10
|
# (classic compiler API; typescript@7's package root is version-only)
|
|
11
11
|
npm install @typescript/typescript6
|
|
12
12
|
# optional, only for vouchington-tooling/agent-blackboard and agent-blackboard CLI commands
|
|
13
|
-
npm install agent-blackboard@^0.
|
|
13
|
+
npm install agent-blackboard@^0.6.0
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
## CLI
|
|
@@ -34,6 +34,7 @@ vouchington gha-output name
|
|
|
34
34
|
vouchington gha-needs-results
|
|
35
35
|
vouchington download-with-diagnostics <url> <destination>
|
|
36
36
|
vouchington download-optional-run-artifacts --pattern 'coverage-*' --dir ./coverage-fallback
|
|
37
|
+
vouchington download-optional-run-artifacts --name coverage-tooling --name coverage-web --dir ./coverage-fallback
|
|
37
38
|
vouchington host-pressure-diagnostics
|
|
38
39
|
vouchington allocate-browser-safe-ports 2 --policy ./policy.json --forbidden-ports ./ports.json
|
|
39
40
|
vouchington diagnose-port-collision --ports "2200 2216"
|
|
@@ -57,7 +58,7 @@ vouchington wait-for-apt-locks
|
|
|
57
58
|
vouchington retrospective-transcript --jsonl /path/to/transcript.jsonl
|
|
58
59
|
vouchington retrospective-facts --pr 49 --repo vouchington/vouchington-infra --raw
|
|
59
60
|
vouchington agent-blackboard probe
|
|
60
|
-
vouchington agent-blackboard journal append --session-id <uuid> --agent codex --version 1 --file note.md
|
|
61
|
+
vouchington agent-blackboard journal append --session-id <uuid> --agent codex --version 1 --file note.md --repository vouchington/vouchington-tooling
|
|
61
62
|
vouchington agent-blackboard journal entries --session-id <uuid>
|
|
62
63
|
vouchington agent-blackboard snapshot partition --snapshot <snapshot.jsonl> --checksum <sha256> --counts <counts.json>
|
|
63
64
|
vouchington agent-blackboard snapshot cleanup --snapshot <snapshot.jsonl> --partition-directory <partitions-dir> --receipt <receipt-json>
|
|
@@ -91,6 +92,17 @@ download`), and extracts each selected name into its own directory. Ordinary abs
|
|
|
91
92
|
`availability=unavailable`. Artifact listing retries up to three times with bounded backoff;
|
|
92
93
|
exhausted transport errors, invalid names, and cancellation remain hard failures.
|
|
93
94
|
|
|
95
|
+
Name mode takes one or more `--name <artifact>` flags (mutually exclusive with `--pattern`), lists
|
|
96
|
+
the run's artifacts once, and extracts every requested name that is present into `<dir>/<name>`.
|
|
97
|
+
Names are matched literally, in request order, with repeats downloaded once; empty names, `.`, `..`,
|
|
98
|
+
and names containing `/`, `\`, or a line break are rejected before anything is listed. Requested
|
|
99
|
+
names absent from the run are skipped with a single bounded `::warning::` (the absent count, the
|
|
100
|
+
first three names, then `and N more`) and never one line per name. `availability=available` means at
|
|
101
|
+
least one requested artifact was downloaded; `availability=unavailable` (exit 0) means none was
|
|
102
|
+
present. A present artifact whose download fails is a hard failure: the helper stops at the first
|
|
103
|
+
one, prints `download failed artifact=<name> exit=<n>`, exits non-zero (a downloader exit of 3 is
|
|
104
|
+
reported as 1 so it cannot be mistaken for absence), and writes no `availability` output.
|
|
105
|
+
|
|
94
106
|
`require-up-to-date` fetches the requested remote branch and fails unless its fetched tip is an
|
|
95
107
|
ancestor of `HEAD`. `gitleaks-directory-scan` builds and scans isolated staged-index and current
|
|
96
108
|
nonignored-working-tree mirrors with an explicit config; `--directory` selects the repository root.
|
|
@@ -125,7 +137,10 @@ GitHub reports a merged PR whose `baseRefName` is `main`; a merged PR into anoth
|
|
|
125
137
|
as not merged to main, and a missing base is unavailable.
|
|
126
138
|
|
|
127
139
|
Agent Blackboard support is optional: only the `agent-blackboard` subpath and its CLI commands
|
|
128
|
-
need `agent-blackboard@^0.
|
|
140
|
+
need `agent-blackboard@^0.6.0`. Snapshot cleanup accepts only package-generated temporary paths.
|
|
141
|
+
`appendJournal` requires a `repositories: string[]` argument. The CLI accepts one or more
|
|
142
|
+
`--repository owner/name` flags; it records the entry's exact repositories and updates the session's
|
|
143
|
+
cumulative repository list before appending.
|
|
129
144
|
Programmatic callers launched from a different workspace directory pass their own module URL as
|
|
130
145
|
`dependencies: { resolveFrom: import.meta.url }`; the CLI defaults to the current package context.
|
|
131
146
|
It captures a target under a private tombstone, validates partition names, permissions, JSONL,
|
|
@@ -10,7 +10,12 @@ export type BlackboardClientModule = {
|
|
|
10
10
|
Sessions: new (connection: BlackboardConnection) => {
|
|
11
11
|
ensure(input: unknown): Promise<{
|
|
12
12
|
status: 'created' | 'exists';
|
|
13
|
+
session: {
|
|
14
|
+
data: Record<string, unknown>;
|
|
15
|
+
archivedAt?: string | null;
|
|
16
|
+
};
|
|
13
17
|
}>;
|
|
18
|
+
patch(input: unknown): Promise<unknown>;
|
|
14
19
|
list(input: unknown): Promise<unknown>;
|
|
15
20
|
get(id: string): Promise<unknown>;
|
|
16
21
|
};
|
|
@@ -32,6 +37,7 @@ export declare function appendJournal(input: {
|
|
|
32
37
|
sessionId: string;
|
|
33
38
|
agent: string;
|
|
34
39
|
version: string;
|
|
40
|
+
repositories: string[];
|
|
35
41
|
markdownFile: string;
|
|
36
42
|
parentSessionId?: string | null;
|
|
37
43
|
timestamp?: string;
|
|
@@ -30,6 +30,7 @@ export async function appendJournal(input) {
|
|
|
30
30
|
assertSessionId(input.sessionId);
|
|
31
31
|
if (input.parentSessionId != null)
|
|
32
32
|
assertSessionId(input.parentSessionId, 'parent session id');
|
|
33
|
+
const repositories = normalizeRepositories(input.repositories, true);
|
|
33
34
|
const timestamp = input.timestamp === undefined ? new Date() : new Date(input.timestamp);
|
|
34
35
|
if (Number.isNaN(timestamp.valueOf()))
|
|
35
36
|
throw new Error('journal timestamp is not a valid date-time');
|
|
@@ -44,18 +45,42 @@ export async function appendJournal(input) {
|
|
|
44
45
|
throw new Error(`note file is empty: ${input.markdownFile}`);
|
|
45
46
|
const connection = resolveBlackboardConnection(input.env);
|
|
46
47
|
const { Sessions, Entries } = await loadClient(input.dependencies);
|
|
47
|
-
|
|
48
|
+
const sessions = new Sessions(connection);
|
|
49
|
+
const ensured = await sessions.ensure({
|
|
48
50
|
id: input.sessionId,
|
|
49
51
|
parentSessionId: input.parentSessionId ?? null,
|
|
50
52
|
agent: input.agent,
|
|
51
53
|
version: input.version,
|
|
52
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 } });
|
|
53
62
|
const entry = await new Entries(connection).append({
|
|
54
63
|
sessionId: input.sessionId,
|
|
55
|
-
data: { type: 'journal', markdown, timestamp: timestamp.toISOString() },
|
|
64
|
+
data: { type: 'journal', markdown, timestamp: timestamp.toISOString(), repositories },
|
|
56
65
|
});
|
|
57
66
|
return `Journaled to agent-blackboard session ${input.sessionId} (entry created at ${entry.createdAt}).`;
|
|
58
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
|
+
}
|
|
59
84
|
export async function readJournal(sessionId, env, dependencies) {
|
|
60
85
|
assertSessionId(sessionId);
|
|
61
86
|
const { Entries } = await loadClient(dependencies);
|
|
@@ -21,6 +21,11 @@ function isSelection(value) {
|
|
|
21
21
|
return false;
|
|
22
22
|
if (value.data !== undefined && !isObject(value.data))
|
|
23
23
|
return false;
|
|
24
|
+
if (value.dataArrayContains !== undefined &&
|
|
25
|
+
(!isObject(value.dataArrayContains) ||
|
|
26
|
+
Object.keys(value.dataArrayContains).length === 0 ||
|
|
27
|
+
Object.entries(value.dataArrayContains).some(([key, member]) => key.length === 0 || typeof member !== 'string' || member.length === 0)))
|
|
28
|
+
return false;
|
|
24
29
|
return (value.inactiveForHours === undefined ||
|
|
25
30
|
(typeof value.inactiveForHours === 'number' &&
|
|
26
31
|
Number.isFinite(value.inactiveForHours) &&
|
|
@@ -51,7 +51,8 @@ async function runSnapshot(args) {
|
|
|
51
51
|
}
|
|
52
52
|
async function runJournal(args) {
|
|
53
53
|
const [action, ...flags] = args;
|
|
54
|
-
const
|
|
54
|
+
const { repositories, remaining } = action === 'append' ? extractRepositories(flags) : { repositories: [], remaining: flags };
|
|
55
|
+
const values = flagsToValues(remaining);
|
|
55
56
|
if (action === 'entries') {
|
|
56
57
|
assertAllowed(values, ['session-id']);
|
|
57
58
|
const sessionId = required(values, 'session-id');
|
|
@@ -76,11 +77,31 @@ async function runJournal(args) {
|
|
|
76
77
|
'parent-session-id',
|
|
77
78
|
'timestamp',
|
|
78
79
|
]);
|
|
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
|
+
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`);
|
|
80
81
|
return 0;
|
|
81
82
|
}
|
|
82
83
|
throw new Error('usage: agent-blackboard journal append|entries');
|
|
83
84
|
}
|
|
85
|
+
function extractRepositories(flags) {
|
|
86
|
+
const repositories = [];
|
|
87
|
+
const remaining = [];
|
|
88
|
+
for (let index = 0; index < flags.length; index += 2) {
|
|
89
|
+
const flag = flags[index];
|
|
90
|
+
const value = flags[index + 1];
|
|
91
|
+
if (!flag?.startsWith('--') || value === undefined)
|
|
92
|
+
throw new Error(`invalid option: ${flag ?? ''}`);
|
|
93
|
+
if (flag === '--repository')
|
|
94
|
+
repositories.push(value);
|
|
95
|
+
else
|
|
96
|
+
remaining.push(flag, value);
|
|
97
|
+
}
|
|
98
|
+
return { repositories, remaining };
|
|
99
|
+
}
|
|
100
|
+
function requiredRepositories(repositories) {
|
|
101
|
+
if (repositories.length === 0)
|
|
102
|
+
throw new Error('--repository is required');
|
|
103
|
+
return repositories;
|
|
104
|
+
}
|
|
84
105
|
function flagsToValues(flags) {
|
|
85
106
|
const values = {};
|
|
86
107
|
for (let index = 0; index < flags.length; index += 2) {
|
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 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 --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
|
|
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 --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";
|
|
2
2
|
export declare function printUsage(stream?: NodeJS.WritableStream): void;
|
package/dist/cli/usage.mjs
CHANGED
|
@@ -83,7 +83,7 @@ gha-workspace-policy [--root <directory>] [--workflow-directory <directory>] [--
|
|
|
83
83
|
gha-output <name>
|
|
84
84
|
gha-needs-results [label]
|
|
85
85
|
download-with-diagnostics <url> <destination> [-- curl-args...]
|
|
86
|
-
download-optional-run-artifacts (--name <name
|
|
86
|
+
download-optional-run-artifacts (--name <name>... | --pattern <pattern>) --dir <directory>
|
|
87
87
|
host-pressure-diagnostics
|
|
88
88
|
allocate-browser-safe-ports [count] [--policy path] [--forbidden-ports path]
|
|
89
89
|
diagnose-port-collision [--ports "2200 2216"] [--output-dir PATH]
|
|
@@ -116,7 +116,7 @@ retrospective-transcript [--session-id ID] [--jsonl PATH] [--projects-dir PATH]
|
|
|
116
116
|
link-skill <name> --source-root <skills-dir> --target-root <consumer-skills-dir> Link a packaged or repository-local skill
|
|
117
117
|
retrospective-facts (--pr NUMBER | --branch NAME | --no-pr) [--repo OWNER/NAME] [--raw]
|
|
118
118
|
agent-blackboard probe
|
|
119
|
-
agent-blackboard journal append --session-id UUID --agent NAME --version VERSION --file PATH [--parent-session-id UUID] [--timestamp ISO8601]
|
|
119
|
+
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]
|
|
120
120
|
agent-blackboard journal entries --session-id UUID
|
|
121
121
|
agent-blackboard snapshot partition --snapshot PATH --checksum SHA256 --counts '{"sessions":N,"entries":N,"records":N,"bytes":N}'
|
|
122
122
|
agent-blackboard snapshot cleanup [--snapshot PATH] [--partition-directory PATH --receipt JSON]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type RunOptions = {
|
|
2
|
+
readonly args?: readonly string[] | ((temporaryDirectory: string) => readonly string[]);
|
|
3
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
4
|
+
readonly ghScript?: string;
|
|
5
|
+
readonly sleepScript?: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function runHelper(options?: RunOptions): {
|
|
8
|
+
output: string;
|
|
9
|
+
temporaryDirectory: string;
|
|
10
|
+
pid: number;
|
|
11
|
+
stdout: string;
|
|
12
|
+
stderr: string;
|
|
13
|
+
status: number | null;
|
|
14
|
+
signal: NodeJS.Signals | null;
|
|
15
|
+
error?: Error;
|
|
16
|
+
};
|
|
17
|
+
export declare function cleanupTemporaryDirectories(): void;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync, } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { delimiter, join, resolve } from 'node:path';
|
|
5
|
+
const script = resolve('packages/vouchington-tooling/scripts/gha/download-optional-run-artifacts.sh');
|
|
6
|
+
const temporaryDirectories = [];
|
|
7
|
+
export function runHelper(options = {}) {
|
|
8
|
+
const temporaryDirectory = mkdtempSync(join(tmpdir(), 'download-optional-run-artifacts-'));
|
|
9
|
+
temporaryDirectories.push(temporaryDirectory);
|
|
10
|
+
const binDirectory = join(temporaryDirectory, 'bin');
|
|
11
|
+
const githubOutput = join(temporaryDirectory, 'github-output');
|
|
12
|
+
const ghPath = join(binDirectory, 'gh');
|
|
13
|
+
mkdirSync(binDirectory);
|
|
14
|
+
writeFileSync(ghPath, options.ghScript ??
|
|
15
|
+
'#!/bin/sh\nif [ "$1" = api ]; then echo transport-download-control; else printf "downloaded %s\\n" "$*"; fi\n');
|
|
16
|
+
chmodSync(ghPath, 0o755);
|
|
17
|
+
if (options.sleepScript !== undefined) {
|
|
18
|
+
const sleepPath = join(binDirectory, 'sleep');
|
|
19
|
+
writeFileSync(sleepPath, options.sleepScript);
|
|
20
|
+
chmodSync(sleepPath, 0o755);
|
|
21
|
+
}
|
|
22
|
+
const args = typeof options.args === 'function'
|
|
23
|
+
? options.args(temporaryDirectory)
|
|
24
|
+
: (options.args ?? [
|
|
25
|
+
'--name',
|
|
26
|
+
'transport-download-control',
|
|
27
|
+
'--dir',
|
|
28
|
+
join(temporaryDirectory, 'coverage-control'),
|
|
29
|
+
]);
|
|
30
|
+
const result = spawnSync('bash', [script, ...args], {
|
|
31
|
+
encoding: 'utf8',
|
|
32
|
+
env: {
|
|
33
|
+
...process.env,
|
|
34
|
+
GITHUB_OUTPUT: githubOutput,
|
|
35
|
+
GITHUB_REPOSITORY: 'owner/repo',
|
|
36
|
+
GITHUB_RUN_ID: '1234',
|
|
37
|
+
PATH: binDirectory + delimiter + process.env.PATH,
|
|
38
|
+
...options.env,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
return {
|
|
42
|
+
...result,
|
|
43
|
+
output: existsSync(githubOutput) ? readFileSync(githubOutput, 'utf8') : '',
|
|
44
|
+
temporaryDirectory,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function cleanupTemporaryDirectories() {
|
|
48
|
+
for (const directory of temporaryDirectories.splice(0))
|
|
49
|
+
rmSync(directory, { force: true, recursive: true });
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vouchington-tooling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Vouchington CLI and extractable tooling libraries.",
|
|
5
5
|
"homepage": "https://github.com/vouchington/vouchington-tooling/tree/main/packages/vouchington-tooling#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -334,13 +334,13 @@
|
|
|
334
334
|
"remark-gfm": "4.0.1",
|
|
335
335
|
"smol-toml": "1.8.0",
|
|
336
336
|
"spdx-expression-parse": "5.0.0",
|
|
337
|
-
"yaml": "2.9.
|
|
337
|
+
"yaml": "2.9.1"
|
|
338
338
|
},
|
|
339
339
|
"devDependencies": {
|
|
340
340
|
"@ast-grep/cli": "0.45.3",
|
|
341
341
|
"@types/picomatch": "^4.0.3",
|
|
342
342
|
"@types/spdx-expression-parse": "4.0.0",
|
|
343
|
-
"agent-blackboard": "^0.
|
|
343
|
+
"agent-blackboard": "^0.6.0"
|
|
344
344
|
},
|
|
345
345
|
"peerDependencies": {
|
|
346
346
|
"typescript": ">=5"
|
|
@@ -2,18 +2,34 @@
|
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
4
|
usage() {
|
|
5
|
-
echo 'usage: download-optional-run-artifacts.sh (--name <name
|
|
5
|
+
echo 'usage: download-optional-run-artifacts.sh (--name <name>... | --pattern <pattern>) --dir <directory>' >&2
|
|
6
6
|
exit 2
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
contains() {
|
|
10
|
+
local needle="$1" item
|
|
11
|
+
shift
|
|
12
|
+
for item in "$@"; do
|
|
13
|
+
[ "$item" != "$needle" ] || return 0
|
|
14
|
+
done
|
|
15
|
+
return 1
|
|
16
|
+
}
|
|
17
|
+
|
|
9
18
|
selector=''
|
|
10
19
|
selector_type=''
|
|
20
|
+
names=()
|
|
11
21
|
destination=''
|
|
12
22
|
while [ "$#" -gt 0 ]; do
|
|
13
23
|
case "$1" in
|
|
14
|
-
--name
|
|
24
|
+
--name)
|
|
25
|
+
{ [ "$selector_type" != pattern ] && [ "$#" -ge 2 ] && [ -n "$2" ]; } || usage
|
|
26
|
+
selector_type=name
|
|
27
|
+
if [ "${#names[@]}" -eq 0 ] || ! contains "$2" "${names[@]}"; then names+=("$2"); fi
|
|
28
|
+
shift 2
|
|
29
|
+
;;
|
|
30
|
+
--pattern)
|
|
15
31
|
[ -z "$selector_type" ] && [ "$#" -ge 2 ] || usage
|
|
16
|
-
selector_type=
|
|
32
|
+
selector_type=pattern
|
|
17
33
|
selector="$2"
|
|
18
34
|
shift 2
|
|
19
35
|
;;
|
|
@@ -26,7 +42,8 @@ while [ "$#" -gt 0 ]; do
|
|
|
26
42
|
esac
|
|
27
43
|
done
|
|
28
44
|
|
|
29
|
-
[ -n "$selector_type" ] && [ -n "$
|
|
45
|
+
[ -n "$selector_type" ] && [ -n "$destination" ] || usage
|
|
46
|
+
[ "$selector_type" != pattern ] || [ -n "$selector" ] || usage
|
|
30
47
|
[ -n "${GITHUB_OUTPUT:-}" ] || { echo 'GITHUB_OUTPUT must be set' >&2; exit 2; }
|
|
31
48
|
[ -n "${GITHUB_REPOSITORY:-}" ] || { echo 'GITHUB_REPOSITORY must be set' >&2; exit 2; }
|
|
32
49
|
[ -n "${GITHUB_RUN_ID:-}" ] || { echo 'GITHUB_RUN_ID must be set' >&2; exit 2; }
|
|
@@ -47,30 +64,13 @@ esac
|
|
|
47
64
|
|
|
48
65
|
validate_artifact_name() {
|
|
49
66
|
case "$1" in
|
|
50
|
-
''
|
|
67
|
+
''|.|..|*/*|*\\*|*$'\n'*|*$'\r'*)
|
|
51
68
|
echo '[optional-run-artifacts] invalid artifact name' >&2
|
|
52
69
|
return 2
|
|
53
70
|
;;
|
|
54
71
|
esac
|
|
55
72
|
}
|
|
56
73
|
|
|
57
|
-
download_exact() {
|
|
58
|
-
artifact="$1"
|
|
59
|
-
directory="$2"
|
|
60
|
-
validate_artifact_name "$artifact" || return $?
|
|
61
|
-
artifacts=$(list_artifact_names) || return $?
|
|
62
|
-
found=0
|
|
63
|
-
while IFS= read -r candidate; do
|
|
64
|
-
if [ "$candidate" = "$artifact" ]; then
|
|
65
|
-
found=1
|
|
66
|
-
break
|
|
67
|
-
fi
|
|
68
|
-
done < <(printf '%s\n' "$artifacts")
|
|
69
|
-
[ "$found" -eq 1 ] || return 3
|
|
70
|
-
echo "[optional-run-artifacts] attempt selector=$selector_type" >&2
|
|
71
|
-
GH_HOST="$github_host" gh run download "$GITHUB_RUN_ID" --repo "$repository" --name "$artifact" --dir "$directory"
|
|
72
|
-
}
|
|
73
|
-
|
|
74
74
|
list_artifact_names_once() {
|
|
75
75
|
GH_HOST="$github_host" gh api \
|
|
76
76
|
--paginate \
|
|
@@ -126,8 +126,47 @@ download_pattern() {
|
|
|
126
126
|
done
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
warn_absent() {
|
|
130
|
+
local requested="$1" count=$(($# - 1)) list='' name
|
|
131
|
+
shift
|
|
132
|
+
for name in "${@:1:3}"; do
|
|
133
|
+
list="${list:+$list, }$name"
|
|
134
|
+
done
|
|
135
|
+
[ "$count" -le 3 ] || list="$list and $((count - 3)) more"
|
|
136
|
+
echo "::warning::Optional same-run artifacts absent: $count of $requested requested (${list//\%/%25})" >&2
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
download_names() {
|
|
140
|
+
local name status listed_names
|
|
141
|
+
listed=()
|
|
142
|
+
present=()
|
|
143
|
+
absent=()
|
|
144
|
+
for name in "${names[@]}"; do
|
|
145
|
+
validate_artifact_name "$name" || return $?
|
|
146
|
+
done
|
|
147
|
+
listed_names=$(list_artifact_names) || return $?
|
|
148
|
+
while IFS= read -r name; do
|
|
149
|
+
listed+=("$name")
|
|
150
|
+
done <<< "$listed_names"
|
|
151
|
+
for name in "${names[@]}"; do
|
|
152
|
+
if contains "$name" "${listed[@]}"; then present+=("$name"); else absent+=("$name"); fi
|
|
153
|
+
done
|
|
154
|
+
echo "[optional-run-artifacts] selection selector=name requested=${#names[@]} present=${#present[@]} absent=${#absent[@]}" >&2
|
|
155
|
+
[ "${#absent[@]}" -eq 0 ] || warn_absent "${#names[@]}" "${absent[@]}"
|
|
156
|
+
[ "${#present[@]}" -gt 0 ] || return 3
|
|
157
|
+
for name in "${present[@]}"; do
|
|
158
|
+
echo "[optional-run-artifacts] attempt selector=name artifact=$name" >&2
|
|
159
|
+
if GH_HOST="$github_host" gh run download "$GITHUB_RUN_ID" --repo "$repository" --name "$name" --dir "$destination/$name"; then :; else
|
|
160
|
+
status=$?
|
|
161
|
+
echo "[optional-run-artifacts] download failed artifact=$name exit=$status" >&2
|
|
162
|
+
[ "$status" -ne 3 ] || status=1
|
|
163
|
+
return "$status"
|
|
164
|
+
fi
|
|
165
|
+
done
|
|
166
|
+
}
|
|
167
|
+
|
|
129
168
|
if [ "$selector_type" = name ]; then
|
|
130
|
-
if
|
|
169
|
+
if download_names; then status=0; else status=$?; fi
|
|
131
170
|
else
|
|
132
171
|
if download_pattern; then status=0; else status=$?; fi
|
|
133
172
|
fi
|
|
@@ -139,7 +178,7 @@ else
|
|
|
139
178
|
130|143) exit "$status" ;;
|
|
140
179
|
3)
|
|
141
180
|
echo 'availability=unavailable' >> "$GITHUB_OUTPUT"
|
|
142
|
-
echo "::warning::Optional same-run artifact unavailable; continuing with validated fallback (selector=$selector_type exit=$status)" >&2
|
|
181
|
+
[ "$selector_type" = name ] || echo "::warning::Optional same-run artifact unavailable; continuing with validated fallback (selector=$selector_type exit=$status)" >&2
|
|
143
182
|
echo "[optional-run-artifacts] result=unavailable selector=$selector_type exit=$status" >&2
|
|
144
183
|
;;
|
|
145
184
|
*)
|
|
@@ -26,17 +26,17 @@ delimiter_prefix=$(printf '%s' "$output_name" | tr '[:lower:]-' '[:upper:]_')
|
|
|
26
26
|
delimiter=''
|
|
27
27
|
attempt=1
|
|
28
28
|
while [[ $attempt -le 10 ]]; do
|
|
29
|
-
if !
|
|
30
|
-
echo '
|
|
29
|
+
if ! random_suffix=$(LC_ALL=C od -An -v -N16 -tx1 /dev/urandom | LC_ALL=C tr -d '[:space:]'); then
|
|
30
|
+
echo 'random suffix generation failed while creating a GitHub output delimiter' >&2
|
|
31
31
|
exit 1
|
|
32
32
|
fi
|
|
33
|
-
if [[
|
|
34
|
-
echo '
|
|
33
|
+
if [[ ! "$random_suffix" =~ ^[[:xdigit:]]{32}$ ]]; then
|
|
34
|
+
echo 'random suffix generator returned an invalid GitHub output delimiter suffix' >&2
|
|
35
35
|
exit 1
|
|
36
36
|
fi
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
delimiter="${delimiter_prefix}_${
|
|
38
|
+
normalized_suffix=$(printf '%s' "$random_suffix" | tr '[:lower:]-' '[:upper:]_')
|
|
39
|
+
delimiter="${delimiter_prefix}_${normalized_suffix}"
|
|
40
40
|
if ! grep -Fq -- "$delimiter" "$payload_file"; then
|
|
41
41
|
break
|
|
42
42
|
fi
|
|
@@ -14,6 +14,9 @@ subagent-identity rules.
|
|
|
14
14
|
evidence, and any tracking reference.
|
|
15
15
|
2. Preserve the provider's required session and parent-session identity. Never invent, guess,
|
|
16
16
|
print, search for, or persist credentials outside its documented mechanism.
|
|
17
|
+
When the provider supports structured repository metadata, keep a cumulative list of canonical
|
|
18
|
+
repositories on each agent's own session and tag each entry with only its relevant repositories.
|
|
19
|
+
Update session metadata before appending, and stop if that update fails.
|
|
17
20
|
3. If the journal service cannot authenticate or persist a mandatory entry, stop and report the
|
|
18
21
|
blocker rather than silently substituting a local file or memory.
|
|
19
22
|
4. Read journal entries oldest-first when preparing a retrospective or clustering follow-ups; use
|
|
@@ -14,6 +14,8 @@ Read the local `AGENTS.md`, `CLAUDE.md`, and journal guidance first.
|
|
|
14
14
|
sources. Do not estimate unknown facts from memory or transcripts.
|
|
15
15
|
3. Summarize the outcome, plan-versus-actual differences, validation evidence, recurring friction,
|
|
16
16
|
and actionable process improvements. Keep one-off noise separate from repeatable root causes.
|
|
17
|
+
Preserve the repository attribution of the source entries in the retrospective record when the
|
|
18
|
+
journal supports it; a retrospective spanning repositories names every represented repository.
|
|
17
19
|
4. Save through the repository's required durable mechanism and report the record identifier plus
|
|
18
20
|
any follow-up decisions.
|
|
19
21
|
|
|
@@ -8,7 +8,9 @@ description: Distill completed session records into a small set of verified, act
|
|
|
8
8
|
Use when completed retrospectives or journals should become durable follow-up work. Read local
|
|
9
9
|
`AGENTS.md`, `CLAUDE.md`, issue policy, and journal retention rules before any mutation.
|
|
10
10
|
|
|
11
|
-
1. Enumerate only completed, eligible session records.
|
|
11
|
+
1. Enumerate only completed, eligible session records. For repository-scoped work, filter sessions
|
|
12
|
+
by repository membership and use only entries attributed to that repository. Keep untagged legacy
|
|
13
|
+
records unclassified instead of inferring their repository. Leave in-progress sessions intact.
|
|
12
14
|
2. Cluster findings by root cause, favoring a few broad actionable themes over many narrow issues.
|
|
13
15
|
Treat a finding already linked to an open tracker as context, not a duplicate.
|
|
14
16
|
3. Verify each candidate against the current base and search existing issues and open changes before
|
|
@@ -17,7 +19,9 @@ Use when completed retrospectives or journals should become durable follow-up wo
|
|
|
17
19
|
validation. Route every authorized creation through
|
|
18
20
|
[github-issue](../github-issue/SKILL.md), including its repository gate, label approval, and
|
|
19
21
|
denied-external tracking behavior.
|
|
20
|
-
5. Archive only records that were fully processed
|
|
22
|
+
5. Archive only records that were fully processed across every represented repository under the
|
|
23
|
+
repository's retention rules. A one-repository pass leaves a multi-repository session active
|
|
24
|
+
until its other repositories have been reviewed. Report
|
|
21
25
|
created, updated, skipped, and deferred themes with reasons.
|
|
22
26
|
|
|
23
27
|
Use source records only for local verification and leave them in the repository's approved journal
|
|
@@ -18,3 +18,6 @@ rules before choosing a runner-specific approach.
|
|
|
18
18
|
|
|
19
19
|
Read [tautological tests](references/tautological-tests.md) before finishing any test whose
|
|
20
20
|
assertion is not obviously falsifiable by a defect in the code under test.
|
|
21
|
+
|
|
22
|
+
Read [dependency boundaries](references/dependency-boundaries.md) before asserting on anything a
|
|
23
|
+
dependency produces, and whenever a dependency upgrade breaks a test.
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Choose the lowest realistic boundary that can observe the contract. Mock external systems and
|
|
4
4
|
uncontrolled infrastructure; exercise internal module composition where practical. Test behavior,
|
|
5
|
-
failure paths, authorization, and security-relevant validation rather than private calls
|
|
5
|
+
failure paths, authorization, and security-relevant validation rather than private calls, and
|
|
6
|
+
assert what this repository owns rather than what its [dependencies](dependency-boundaries.md)
|
|
7
|
+
produce.
|
|
6
8
|
|
|
7
9
|
Start with a failing test when the behavior is testable. Finish only when the production path, its
|
|
8
10
|
public contract, documentation, and generated artifacts move together. Do not leave placeholders or
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Dependency boundaries
|
|
2
|
+
|
|
3
|
+
Test the code this repository owns. A dependency's behavior — its algorithms, error messages, output
|
|
4
|
+
formats, internal file layout, exit codes, the URLs and query strings it builds, its pagination and
|
|
5
|
+
retry strategy, and the order or number of calls it makes — belongs to that dependency's own tests.
|
|
6
|
+
An assertion that pins any of it fails when the dependency ships a release rather than when this
|
|
7
|
+
repository regresses, so every upgrade arrives as a red build that no change here caused.
|
|
8
|
+
|
|
9
|
+
Apply the [tautological tests](tautological-tests.md) falsifiability check with ownership added: for
|
|
10
|
+
every assertion, name a defect in this repository's code that would make it fail. If only a
|
|
11
|
+
dependency release could fail it, delete it. If it covers owned logic but also pins incidental
|
|
12
|
+
dependency detail, rewrite it to assert the owned outcome. What the repository chooses or promises
|
|
13
|
+
is owned even when a dependency carries it out — the rules it enables, its thresholds and scopes,
|
|
14
|
+
the arguments, endpoints, and parameters it passes, how it handles a dependency's exit codes and
|
|
15
|
+
errors, and any call count or order its own logic requires — so test it, but through the
|
|
16
|
+
dependency's public API rather than its internals.
|
|
17
|
+
|
|
18
|
+
Watch for these shapes. A version-drift guard deep-imports a package's private files or calls its
|
|
19
|
+
internal functions so the suite notices when upstream behavior changes; it couples the suite to a
|
|
20
|
+
layout the package never promised, so delete it, and when documentation relies on dependency
|
|
21
|
+
behavior, link to the dependency's own documentation instead of restating and pinning it. A
|
|
22
|
+
dependency re-test runs a dependency with fabricated input and asserts what it returns; a wrapper
|
|
23
|
+
that only forwards configuration needs one test proving the configuration arrives, not a second copy
|
|
24
|
+
of the dependency's suite. An upstream-output pin asserts exact dependency-owned messages, markdown,
|
|
25
|
+
JSON envelopes, or renderer markup when the owned contract is narrower; assert the rule identifier,
|
|
26
|
+
status, or owned field instead. A sequence-bound fake answers by call order — chained one-shot mock
|
|
27
|
+
responses, or a queue of canned replies consumed in turn — when the code under test does not own
|
|
28
|
+
that order, so a dependency that adds a request, paginates differently, or reorders its calls hands
|
|
29
|
+
the wrong reply to the wrong call; route the fake by request meaning, such as method and pathname,
|
|
30
|
+
and assert the owned result rather than dependency-incidental URL, query-string, or call-count
|
|
31
|
+
details.
|
|
32
|
+
|
|
33
|
+
When a dependency upgrade breaks a test, first ask whether the test pinned dependency behavior. If
|
|
34
|
+
it did, fix the test by deleting or rewriting it rather than re-pinning the new upstream value, and
|
|
35
|
+
do not add a guard that fails on the next release. If the failure traces to owned code or owned
|
|
36
|
+
configuration — the dependency now rejects arguments this repository passes, or returns a result
|
|
37
|
+
this repository's logic mishandles — it is a real regression, so fix the owned code instead.
|