vouchington-tooling 0.3.2 → 0.3.4

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 CHANGED
@@ -89,6 +89,8 @@ as not merged to main, and a missing base is unavailable.
89
89
 
90
90
  Agent Blackboard support is optional: only the `agent-blackboard` subpath and its CLI commands
91
91
  need `agent-blackboard@^0.3.1`. Snapshot cleanup accepts only package-generated temporary paths.
92
+ Programmatic callers launched from a different workspace directory pass their own module URL as
93
+ `dependencies: { resolveFrom: import.meta.url }`; the CLI defaults to the current package context.
92
94
  It captures a target under a private tombstone, validates partition names, permissions, JSONL,
93
95
  ordering, terminal manifests, and the identity-bound cleanup receipt before deleting files, and
94
96
  restores the original path on a validation failure. Once deletion begins, it retains a private
@@ -24,6 +24,7 @@ export type BlackboardClientModule = {
24
24
  type BlackboardClientLoader = () => Promise<BlackboardClientModule>;
25
25
  export type BlackboardClientDependencies = {
26
26
  loadClient?: BlackboardClientLoader;
27
+ resolveFrom?: string | URL;
27
28
  };
28
29
  export declare function resolveBlackboardConnection(env?: NodeJS.ProcessEnv): BlackboardConnection;
29
30
  export declare function probeBlackboard(env?: NodeJS.ProcessEnv, dependencies?: BlackboardClientDependencies): Promise<void>;
@@ -1,4 +1,15 @@
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
9
  import { readFile } from 'node:fs/promises';
10
+ import { createRequire } from 'node:module';
11
+ import { resolve } from 'node:path';
12
+ import { pathToFileURL } from 'node:url';
2
13
  import { assertSessionId } from './session-id.mjs';
3
14
  export { cleanupSnapshotPartitions, partitionSnapshot } from './snapshot.mjs';
4
15
  export { assertSessionId } from './session-id.mjs';
@@ -12,7 +23,7 @@ export function resolveBlackboardConnection(env = process.env) {
12
23
  return { baseUrl, token, readRetry: {} };
13
24
  }
14
25
  export async function probeBlackboard(env, dependencies) {
15
- const { Sessions } = await loadClient(dependencies?.loadClient);
26
+ const { Sessions } = await loadClient(dependencies);
16
27
  await new Sessions(resolveBlackboardConnection(env)).list({ limit: 1 });
17
28
  }
18
29
  export async function appendJournal(input) {
@@ -32,7 +43,7 @@ export async function appendJournal(input) {
32
43
  if (!markdown)
33
44
  throw new Error(`note file is empty: ${input.markdownFile}`);
34
45
  const connection = resolveBlackboardConnection(input.env);
35
- const { Sessions, Entries } = await loadClient(input.dependencies?.loadClient);
46
+ const { Sessions, Entries } = await loadClient(input.dependencies);
36
47
  await new Sessions(connection).ensure({
37
48
  id: input.sessionId,
38
49
  parentSessionId: input.parentSessionId ?? null,
@@ -47,7 +58,7 @@ export async function appendJournal(input) {
47
58
  }
48
59
  export async function readJournal(sessionId, env, dependencies) {
49
60
  assertSessionId(sessionId);
50
- const { Entries } = await loadClient(dependencies?.loadClient);
61
+ const { Entries } = await loadClient(dependencies);
51
62
  const entries = [];
52
63
  for await (const entry of new Entries(resolveBlackboardConnection(env)).get({
53
64
  sessionId,
@@ -79,16 +90,24 @@ export function formatJournalEntries(sessionId, entries) {
79
90
  .map(({ createdAt, markdown }) => `## ${createdAt}\n\n${markdown}`)
80
91
  .join('\n\n');
81
92
  }
82
- async function loadClient(loader = defaultClientLoader) {
93
+ async function loadClient(dependencies = {}) {
94
+ const loader = dependencies.loadClient ?? (() => defaultClientLoader(dependencies.resolveFrom));
83
95
  try {
84
96
  return await loader();
85
97
  }
86
98
  catch (error) {
87
- if (error instanceof Error && 'code' in error && error.code === 'ERR_MODULE_NOT_FOUND')
88
- throw new Error('agent-blackboard is not installed; install the optional agent-blackboard peer dependency', { cause: error });
99
+ if (isMissingModuleError(error))
100
+ throw new Error('agent-blackboard is not installed; install it alongside vouchington-tooling to use this integration', { cause: error });
89
101
  throw error;
90
102
  }
91
103
  }
92
- async function defaultClientLoader() {
93
- return (await import('agent-blackboard'));
104
+ async function defaultClientLoader(resolveFrom) {
105
+ const consumerRequire = createRequire(resolveFrom ?? resolve(process.cwd(), 'package.json'));
106
+ const specifier = pathToFileURL(consumerRequire.resolve('agent-blackboard')).href;
107
+ return (await import(__rewriteRelativeImportExtension(specifier)));
108
+ }
109
+ function isMissingModuleError(error) {
110
+ return (error instanceof Error &&
111
+ 'code' in error &&
112
+ (error.code === 'ERR_MODULE_NOT_FOUND' || error.code === 'MODULE_NOT_FOUND'));
94
113
  }
@@ -33,7 +33,7 @@ export function writePostedOutput(posted, outputPath = process.env.GITHUB_OUTPUT
33
33
  return;
34
34
  appendFileSync(outputPath, `posted=${posted ? 'true' : 'false'}\n`);
35
35
  }
36
- function readPullRefs(repository, prNumber, exec) {
36
+ function readPullState(repository, prNumber, exec) {
37
37
  let lastError;
38
38
  for (let attempt = 1; attempt <= 3; attempt += 1) {
39
39
  try {
@@ -41,7 +41,7 @@ function readPullRefs(repository, prNumber, exec) {
41
41
  'api',
42
42
  `repos/${repository}/pulls/${prNumber}`,
43
43
  '--jq',
44
- '[.head.sha, .base.sha] | @tsv',
44
+ '[.head.sha, .base.sha, .draft, .state] | @tsv',
45
45
  ]).split('\t');
46
46
  }
47
47
  catch (error) {
@@ -72,14 +72,20 @@ export function createGhPostReviewIo(options) {
72
72
  if (expectedBaseSha && !/^[0-9a-f]{40}$/u.test(expectedBaseSha)) {
73
73
  throw new ReviewPayloadError('EXPECTED_BASE_SHA must be a full lowercase commit SHA.');
74
74
  }
75
- const refs = readPullRefs(repository, prNumber, exec);
76
- const [headSha = '', baseSha = ''] = refs;
75
+ const state = readPullState(repository, prNumber, exec);
76
+ const [headSha = '', baseSha = '', draft = '', pullState = ''] = state;
77
77
  if (!/^[0-9a-f]{40}$/u.test(headSha)) {
78
78
  throw new ReviewPayloadError(`Could not resolve PR head SHA (got "${headSha}").`);
79
79
  }
80
80
  if (!/^[0-9a-f]{40}$/u.test(baseSha)) {
81
81
  throw new ReviewPayloadError(`Could not resolve PR base SHA (got "${baseSha}").`);
82
82
  }
83
+ if (draft !== 'false') {
84
+ throw new ReviewPayloadError('Pull request became a draft before posting the review.');
85
+ }
86
+ if (pullState !== 'open') {
87
+ throw new ReviewPayloadError('Pull request closed before posting the review.');
88
+ }
83
89
  if (expectedHeadSha && headSha !== expectedHeadSha) {
84
90
  throw new ReviewPayloadError('PR head changed before posting the selected review.');
85
91
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vouchington-tooling",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
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": {
@@ -252,14 +252,6 @@
252
252
  "@types/picomatch": "^4.0.3",
253
253
  "agent-blackboard": "^0.3.1"
254
254
  },
255
- "peerDependencies": {
256
- "agent-blackboard": "^0.3.1"
257
- },
258
- "peerDependenciesMeta": {
259
- "agent-blackboard": {
260
- "optional": true
261
- }
262
- },
263
255
  "optionalDependencies": {
264
256
  "@libpg-query/parser": "^18.0.0"
265
257
  },
@@ -8,13 +8,43 @@ description: Use when editing GitHub Actions workflows or composite actions to k
8
8
  Use before editing a workflow or composite action. Repository-local instructions own runners,
9
9
  approved action pinning, concurrency naming, secrets, permissions, and workflow-only PR rules.
10
10
 
11
+ Apply this portable baseline unless a stricter repository-local rule overrides it:
12
+
13
+ - Use `pull_request` for pull-request workflows in private repositories. Reserve
14
+ `pull_request_target` for base-owned orchestration in public repositories or narrowly scoped
15
+ Dependabot/Renovate automation. A privileged workflow must never check out or execute untrusted
16
+ pull-request content.
17
+ - Load [github-actions-authoring](../github-actions-authoring/SKILL.md) when changing orchestration.
18
+ Do not poll remote workflow, deployment, lease, service, or health state.
19
+ - Give every concrete job a timeout of no more than 30 minutes. A caller job that invokes a reusable
20
+ workflow through top-level `jobs.<job_id>.uses` cannot accept `timeout-minutes`; enforce the bound
21
+ on every concrete job inside the called workflow. If the underlying operation cannot terminate
22
+ inside that bound, decompose it into event-driven phases; lowering or moving the timeout alone does
23
+ not fix the design. Preserve a required job or check name with a bounded fan-in job when splitting
24
+ work would otherwise change the repository's merge contract. Each underlying phase must also have
25
+ a deadline of no more than 30 minutes and support cancellation, rollback, or an explicit terminal
26
+ retained/recovery state. An event callback may report completion; it must not hide a longer-running
27
+ operation in another service.
28
+ - Use GitHub-hosted runners only for public repositories. Private repositories use the consumer's
29
+ approved self-hosted or disposable runner labels.
30
+ - Pin every repository-backed external `uses:` reference—anything other than a local `./...`
31
+ action—to a full lowercase 40-character Git SHA followed immediately by its machine-maintainable
32
+ version comment, such as `# v4.2.0`, so Dependabot can update both. Pin `docker://...` actions to an
33
+ immutable `@sha256:` image digest instead of a Git SHA. Keep GitHub Actions dependency updates
34
+ enabled.
35
+ - Workflow tests and fixtures must not assert an action dependency's exact SHA or version. Assert
36
+ the action identity and Git SHA shape, or derive the dependency ref from the workflow under test,
37
+ so dependency-update pull requests can change pins without synchronized fixture edits. This does
38
+ not prohibit asserting an exact source revision in `with.ref` when exact-head checkout is a
39
+ workflow security invariant.
40
+
11
41
  1. Read every applicable `AGENTS.md` and `CLAUDE.md` from the repository root through the workflow,
12
42
  plus relevant CI documentation and callers. Apply the closest instruction only when rules
13
43
  conflict. Identify trusted and untrusted inputs and every credential boundary.
14
44
  2. Give each job the least permissions it needs. Keep untrusted pull-request content out of shell
15
45
  interpolation, privileged tokens, and write-capable steps.
16
- 3. Use the repository's pinned-action and runner policy. Keep checkout refs, artifact boundaries,
17
- caches, and concurrency behavior explicit.
46
+ 3. Apply the portable pinning, runner, trigger, and timeout baseline plus any stricter consumer
47
+ policy. Keep checkout refs, artifact boundaries, caches, and concurrency behavior explicit.
18
48
  4. Validate changed YAML with the local workflow checker and run the affected workflow tests or
19
49
  scripts. Update local CI documentation when behavior or operator expectations change.
20
50
  5. Review the final diff for privilege escalation, accidental secret exposure, unsafe quoting,
@@ -4,7 +4,7 @@
4
4
  {
5
5
  "name": "agent-workflow",
6
6
  "plugin": "vouchington-workflow",
7
- "pluginVersion": "0.4.0",
7
+ "pluginVersion": "0.5.1",
8
8
  "path": "agent-workflow/SKILL.md"
9
9
  },
10
10
  {
@@ -17,7 +17,7 @@
17
17
  {
18
18
  "name": "blackboard",
19
19
  "plugin": "vouchington-workflow",
20
- "pluginVersion": "0.4.0",
20
+ "pluginVersion": "0.5.1",
21
21
  "path": "blackboard/SKILL.md"
22
22
  },
23
23
  {
@@ -29,26 +29,26 @@
29
29
  {
30
30
  "name": "git-commit-checklist",
31
31
  "plugin": "vouchington-workflow",
32
- "pluginVersion": "0.4.0",
32
+ "pluginVersion": "0.5.1",
33
33
  "path": "git-commit-checklist/SKILL.md"
34
34
  },
35
35
  {
36
36
  "name": "github-actions-authoring",
37
37
  "plugin": "vouchington-workflow",
38
- "pluginVersion": "0.4.0",
38
+ "pluginVersion": "0.5.1",
39
39
  "path": "github-actions-authoring/SKILL.md",
40
40
  "prerequisites": ["github-actions-checklist"]
41
41
  },
42
42
  {
43
43
  "name": "github-actions-checklist",
44
44
  "plugin": "vouchington-workflow",
45
- "pluginVersion": "0.4.0",
45
+ "pluginVersion": "0.5.1",
46
46
  "path": "github-actions-checklist/SKILL.md"
47
47
  },
48
48
  {
49
49
  "name": "github-issue",
50
50
  "plugin": "vouchington-workflow",
51
- "pluginVersion": "0.4.0",
51
+ "pluginVersion": "0.5.1",
52
52
  "path": "github-issue/SKILL.md"
53
53
  },
54
54
  {
@@ -58,22 +58,28 @@
58
58
  "path": "nextjs-vitest-test-authoring/SKILL.md",
59
59
  "prerequisites": ["vitest-test-authoring"]
60
60
  },
61
+ {
62
+ "name": "npm-publishing",
63
+ "plugin": "vouchington-workflow",
64
+ "pluginVersion": "0.5.1",
65
+ "path": "npm-publishing/SKILL.md"
66
+ },
61
67
  {
62
68
  "name": "organize-github-issues",
63
69
  "plugin": "vouchington-workflow",
64
- "pluginVersion": "0.4.0",
70
+ "pluginVersion": "0.5.1",
65
71
  "path": "organize-github-issues/SKILL.md"
66
72
  },
67
73
  {
68
74
  "name": "package-json-checklist",
69
75
  "plugin": "vouchington-workflow",
70
- "pluginVersion": "0.4.0",
76
+ "pluginVersion": "0.5.1",
71
77
  "path": "package-json-checklist/SKILL.md"
72
78
  },
73
79
  {
74
80
  "name": "planning",
75
81
  "plugin": "vouchington-workflow",
76
- "pluginVersion": "0.4.0",
82
+ "pluginVersion": "0.5.1",
77
83
  "path": "planning/SKILL.md"
78
84
  },
79
85
  {
@@ -97,43 +103,43 @@
97
103
  {
98
104
  "name": "pr-description",
99
105
  "plugin": "vouchington-workflow",
100
- "pluginVersion": "0.4.0",
106
+ "pluginVersion": "0.5.1",
101
107
  "path": "pr-description/SKILL.md"
102
108
  },
103
109
  {
104
110
  "name": "retrospective",
105
111
  "plugin": "vouchington-workflow",
106
- "pluginVersion": "0.4.0",
112
+ "pluginVersion": "0.5.1",
107
113
  "path": "retrospective/SKILL.md"
108
114
  },
109
115
  {
110
116
  "name": "retrospective-distill",
111
117
  "plugin": "vouchington-workflow",
112
- "pluginVersion": "0.4.0",
118
+ "pluginVersion": "0.5.1",
113
119
  "path": "retrospective-distill/SKILL.md"
114
120
  },
115
121
  {
116
122
  "name": "review-ci-logs",
117
123
  "plugin": "vouchington-workflow",
118
- "pluginVersion": "0.4.0",
124
+ "pluginVersion": "0.5.1",
119
125
  "path": "review-ci-logs/SKILL.md"
120
126
  },
121
127
  {
122
128
  "name": "review-github-issue-taxonomy",
123
129
  "plugin": "vouchington-workflow",
124
- "pluginVersion": "0.4.0",
130
+ "pluginVersion": "0.5.1",
125
131
  "path": "review-github-issue-taxonomy/SKILL.md"
126
132
  },
127
133
  {
128
134
  "name": "revisit-followups",
129
135
  "plugin": "vouchington-workflow",
130
- "pluginVersion": "0.4.0",
136
+ "pluginVersion": "0.5.1",
131
137
  "path": "revisit-followups/SKILL.md"
132
138
  },
133
139
  {
134
140
  "name": "static-analysis-checklist",
135
141
  "plugin": "vouchington-workflow",
136
- "pluginVersion": "0.4.0",
142
+ "pluginVersion": "0.5.1",
137
143
  "path": "static-analysis-checklist/SKILL.md"
138
144
  },
139
145
  {
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: npm-publishing
3
+ description: Bootstrap an npm package and give a human the exact commands to publish it publicly and configure GitHub trusted publishing.
4
+ ---
5
+
6
+ # npm publishing bootstrap
7
+
8
+ Prepare the local package, but leave public registry and trusted-publisher mutations to the human.
9
+ Read every applicable `AGENTS.md` and `CLAUDE.md` from the repository root through the package.
10
+ Repository-local policy and the consumer wrapper own the package path and name, npm scope, GitHub
11
+ repository, release workflow, build commands, and whether a laptop may publish.
12
+
13
+ ## Prepare the package
14
+
15
+ 1. Resolve the absolute repository root, package directory relative to that root, exact npm package
16
+ name, `owner/repository`, and release workflow filename. The workflow value is the case-sensitive
17
+ filename under `.github/workflows`, not a path. Do not leave placeholders in the final commands.
18
+ 2. Confirm npm 11.15 or newer, account-level two-factor authentication, and write access to the npm
19
+ package or scope. Inspect the workflow on the repository's default branch: it must grant
20
+ `id-token: write`, use a supported runner, install a trusted-publishing-capable npm version, and
21
+ publish this package.
22
+ 3. When the package does not exist in the registry, create the smallest useful stub module in its
23
+ intended directory. Follow neighboring package conventions for metadata, source, exports, types,
24
+ license, README, build output, and workspace registration. Do not replace an existing module with
25
+ a stub or publish a version already present in the registry.
26
+ 4. Build the package as required, then inspect the publication lifecycle and exact payload with
27
+ `npm publish <package-directory> --access public --dry-run`. Stop if it includes secrets,
28
+ environment files, source maps, unrelated workspace files, or missing runtime/type entrypoints.
29
+ This dry run must include `prepublishOnly`; a pack-only check is insufficient when that lifecycle
30
+ script can change the payload.
31
+ 5. If the package already exists, have the human run `npm trust list <package-name>` before any
32
+ mutation. Omit the initial publish command. If a trust relationship already exists, report it and
33
+ omit the trust-creation command rather than revoking or replacing it.
34
+
35
+ ## Hand off the mutations
36
+
37
+ Do not run a real `npm publish` or a mutating `npm trust` subcommand. Explain that the first command
38
+ creates an externally visible, effectively irreversible package version and the second grants the
39
+ named workflow publish authority. Include the publish command only when bootstrapping a package that
40
+ does not exist; an existing package proceeds directly to trust setup when its trust list is empty.
41
+ Tell the human to append a current one-time password after each final `--otp=` without sharing or
42
+ recording it. Warn that the requested `--otp=` form can expose the OTP in shell history and process
43
+ arguments, and tell the human to follow their local secret-handling policy. Give the applicable
44
+ commands with fully resolved values and in this order:
45
+
46
+ ```sh
47
+ cd /absolute/repository/root
48
+ npm publish ./relative/package-directory --access public --otp=
49
+ npm trust github @scope/package \
50
+ --repo owner/repository \
51
+ --file release.yml \
52
+ --allow-publish \
53
+ --yes \
54
+ --otp=
55
+ ```
56
+
57
+ The `cd` target must be the directory from which the relative publish path resolves. If local policy
58
+ prohibits publishing from the current machine, say so and identify the approved environment while
59
+ preserving the same ordered commands. Ask the human to confirm the package payload, package name,
60
+ repository, workflow filename, and publish permission immediately before running them.
61
+
62
+ Afterward, have the human verify any newly published package version and run
63
+ `npm trust list @scope/package` again. Do not retry authorization or registry failures blindly, and do
64
+ not fall back to a long-lived npm token without explicit direction.