vouchington-tooling 0.41.0 → 0.43.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/bin/policy-runner.mjs +26 -0
- package/dist/agent-blackboard/feedback-codec.d.mts +1 -0
- package/dist/agent-blackboard/feedback-codec.mjs +1 -1
- package/dist/cli/usage.d.mts +1 -1
- package/dist/cli/usage.mjs +3 -1
- package/dist/gh-cli/pr-create.d.mts +2 -0
- package/dist/gh-cli/pr-create.mjs +5 -1
- package/dist/mcp-server/context.d.mts +22 -2
- package/dist/mcp-server/context.mjs +14 -1
- package/dist/mcp-server/dispatch.d.mts +11 -0
- package/dist/mcp-server/dispatch.mjs +12 -0
- package/dist/mcp-server/handlers-journal.d.mts +14 -1
- package/dist/mcp-server/handlers-journal.mjs +14 -9
- package/dist/mcp-server/handlers-pr.d.mts +4 -0
- package/dist/mcp-server/handlers-pr.mjs +143 -0
- package/dist/mcp-server/handlers-retrospective.d.mts +12 -0
- package/dist/mcp-server/handlers-retrospective.mjs +154 -0
- package/dist/mcp-server/harness.test-helpers.d.mts +10 -1
- package/dist/mcp-server/harness.test-helpers.mjs +4 -1
- package/dist/mcp-server/journal-read.d.mts +9 -0
- package/dist/mcp-server/journal-read.mjs +33 -0
- package/dist/mcp-server/policy-config.d.mts +6 -0
- package/dist/mcp-server/policy-config.mjs +60 -0
- package/dist/mcp-server/policy-run.d.mts +40 -0
- package/dist/mcp-server/policy-run.mjs +116 -0
- package/dist/mcp-server/policy-spawn.d.mts +24 -0
- package/dist/mcp-server/policy-spawn.mjs +97 -0
- package/dist/mcp-server/pr-body-file.d.mts +4 -0
- package/dist/mcp-server/pr-body-file.mjs +22 -0
- package/dist/mcp-server/pr-body.d.mts +20 -0
- package/dist/mcp-server/pr-body.mjs +47 -0
- package/dist/mcp-server/pr-defaults.d.mts +16 -0
- package/dist/mcp-server/pr-defaults.mjs +35 -0
- package/dist/mcp-server/pr-gh.d.mts +16 -0
- package/dist/mcp-server/pr-gh.mjs +56 -0
- package/dist/mcp-server/pr-input.d.mts +26 -0
- package/dist/mcp-server/pr-input.mjs +62 -0
- package/dist/mcp-server/pr-journal.d.mts +12 -0
- package/dist/mcp-server/pr-journal.mjs +86 -0
- package/dist/mcp-server/pr-live.d.mts +24 -0
- package/dist/mcp-server/pr-live.mjs +49 -0
- package/dist/mcp-server/pr-tools.test-helpers.d.mts +54 -0
- package/dist/mcp-server/pr-tools.test-helpers.mjs +67 -0
- package/dist/mcp-server/retrospective-facts-input.d.mts +6 -0
- package/dist/mcp-server/retrospective-facts-input.mjs +25 -0
- package/dist/mcp-server/retrospective-front-matter.d.mts +22 -0
- package/dist/mcp-server/retrospective-front-matter.mjs +88 -0
- package/dist/mcp-server/retrospective-input.d.mts +29 -0
- package/dist/mcp-server/retrospective-input.mjs +145 -0
- package/dist/mcp-server/retrospective-resolve.d.mts +15 -0
- package/dist/mcp-server/retrospective-resolve.mjs +41 -0
- package/dist/mcp-server/tools-pr.d.mts +2 -0
- package/dist/mcp-server/tools-pr.mjs +69 -0
- package/dist/mcp-server/tools-retrospective.d.mts +2 -0
- package/dist/mcp-server/tools-retrospective.mjs +48 -0
- package/dist/mcp-server/tools-schema.d.mts +54 -0
- package/dist/mcp-server/tools-schema.mjs +70 -0
- package/dist/mcp-server/tools.d.mts +1 -18
- package/dist/mcp-server/tools.mjs +6 -70
- package/dist/mcp-server/worktree.d.mts +11 -4
- package/dist/mcp-server/worktree.mjs +31 -6
- package/docs/mcp-server.md +164 -16
- package/package.json +1 -1
- package/skills/pr-description/SKILL.md +8 -0
- package/skills/retrospective/SKILL.md +5 -2
|
@@ -1,62 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
pattern: ID_PATTERN,
|
|
5
|
-
maxLength: 256,
|
|
6
|
-
description: 'Explicit session id (URL-safe). Never inferred by the server.',
|
|
7
|
-
};
|
|
8
|
-
const WORKTREE = {
|
|
9
|
-
type: 'string',
|
|
10
|
-
description: 'Absolute path of the top level of a git worktree (`git rev-parse --show-toplevel`), not a ' +
|
|
11
|
-
'subdirectory. Defaults to the launch directory worktree; required when the server was ' +
|
|
12
|
-
'launched outside a git worktree.',
|
|
13
|
-
};
|
|
14
|
-
const PARENT_SESSION_ID = {
|
|
15
|
-
type: ['string', 'null'],
|
|
16
|
-
pattern: ID_PATTERN,
|
|
17
|
-
maxLength: 256,
|
|
18
|
-
description: 'Direct parent session id, or null for a root session.',
|
|
19
|
-
};
|
|
20
|
-
const AGENT = { type: 'string', maxLength: 128, description: 'Agent name.' };
|
|
21
|
-
const VERSION = { type: 'string', maxLength: 128, description: 'Agent version.' };
|
|
22
|
-
const OUTCOMES = [
|
|
23
|
-
'in-progress',
|
|
24
|
-
'success',
|
|
25
|
-
'failure',
|
|
26
|
-
'cancelled',
|
|
27
|
-
'timed-out',
|
|
28
|
-
'no-change',
|
|
29
|
-
'policy-refusal',
|
|
30
|
-
'unknown',
|
|
31
|
-
];
|
|
32
|
-
const COVERAGE_STATUSES = ['complete', 'partial', 'unavailable', 'not-assessed', 'not-started'];
|
|
33
|
-
const READ_ONLY = flags(true, false, true);
|
|
34
|
-
const ADDITIVE = flags(false, false, false);
|
|
35
|
-
const IDEMPOTENT_ADDITIVE = flags(false, false, true);
|
|
36
|
-
const MUTATING = flags(false, true, true);
|
|
37
|
-
function flags(readOnly, destructive, idempotent) {
|
|
38
|
-
return {
|
|
39
|
-
readOnlyHint: readOnly,
|
|
40
|
-
destructiveHint: destructive,
|
|
41
|
-
idempotentHint: idempotent,
|
|
42
|
-
openWorldHint: false,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
function tool(name, description, annotations, properties = {}, required = []) {
|
|
46
|
-
return {
|
|
47
|
-
name,
|
|
48
|
-
description,
|
|
49
|
-
inputSchema: {
|
|
50
|
-
type: 'object',
|
|
51
|
-
properties: { sessionId: SESSION_ID, worktree: WORKTREE, ...properties },
|
|
52
|
-
required: ['sessionId', ...required],
|
|
53
|
-
additionalProperties: false,
|
|
54
|
-
},
|
|
55
|
-
annotations,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
const IDENTITY = { parentSessionId: PARENT_SESSION_ID, agent: AGENT, version: VERSION };
|
|
59
|
-
const IDENTITY_REQUIRED = ['parentSessionId', 'agent', 'version'];
|
|
1
|
+
import { ADDITIVE, AGENT, COVERAGE, IDEMPOTENT_ADDITIVE, IDENTITY, IDENTITY_REQUIRED, ID_PATTERN, MUTATING, OUTCOMES, PARENT_SESSION_ID, READ_ONLY, VERSION, tool, } from "./tools-schema.mjs";
|
|
2
|
+
import { PR_TOOLS } from "./tools-pr.mjs";
|
|
3
|
+
import { RETROSPECTIVE_TOOLS } from "./tools-retrospective.mjs";
|
|
60
4
|
export const TOOLS = [
|
|
61
5
|
tool('journal_append', 'Appends one journal entry to the caller session and returns the verified read-back receipt. ' +
|
|
62
6
|
'Nothing is written unless every required field validates. Interactive mode retains the ' +
|
|
@@ -78,17 +22,7 @@ export const TOOLS = [
|
|
|
78
22
|
minItems: 1,
|
|
79
23
|
description: 'Exact owner/name repositories this entry concerns.',
|
|
80
24
|
},
|
|
81
|
-
feedbackCoverage:
|
|
82
|
-
type: 'object',
|
|
83
|
-
properties: {
|
|
84
|
-
status: { enum: COVERAGE_STATUSES },
|
|
85
|
-
sources: { type: 'array', items: { type: 'string' } },
|
|
86
|
-
droppedCount: { type: 'integer', minimum: 0 },
|
|
87
|
-
},
|
|
88
|
-
required: ['status'],
|
|
89
|
-
additionalProperties: false,
|
|
90
|
-
description: 'Explicit coverage; sources default to [] and droppedCount to 0.',
|
|
91
|
-
},
|
|
25
|
+
feedbackCoverage: COVERAGE,
|
|
92
26
|
category: { type: 'string', description: 'Optional context; never changes entry type.' },
|
|
93
27
|
}, [
|
|
94
28
|
...IDENTITY_REQUIRED,
|
|
@@ -112,6 +46,8 @@ export const TOOLS = [
|
|
|
112
46
|
'as sourceEventId and diagnostic.', IDEMPOTENT_ADDITIVE),
|
|
113
47
|
tool('session_ensure', 'Idempotently creates a session, or verifies that an existing one has the same identity. ' +
|
|
114
48
|
'Returns the session id and whether it was created.', IDEMPOTENT_ADDITIVE, IDENTITY, IDENTITY_REQUIRED),
|
|
49
|
+
...RETROSPECTIVE_TOOLS,
|
|
50
|
+
...PR_TOOLS,
|
|
115
51
|
tool('snapshot_export', 'Exports non-archived sessions into a private local JSONL file chosen by the blackboard ' +
|
|
116
52
|
'client. Returns the path, counts, checksum, manifest, and cleanupToken; never the records. ' +
|
|
117
53
|
'sessionId identifies the caller.', ADDITIVE, {
|
|
@@ -11,12 +11,19 @@ export declare const runIsolatedGit: RunTextCommand;
|
|
|
11
11
|
* or an unsafe repository, is thrown with the cause rather than reported as "outside a worktree".
|
|
12
12
|
*/
|
|
13
13
|
export declare function launchWorktreeRoot(cwd: string, runGit: RunTextCommand): Promise<string | undefined>;
|
|
14
|
+
/**
|
|
15
|
+
* The worktree paths in `git worktree list --porcelain -z` output: NUL-terminated fields, an empty
|
|
16
|
+
* field ending each record, so a path containing a newline cannot forge a record.
|
|
17
|
+
*/
|
|
18
|
+
export declare function parseWorktreeList(output: string): string[];
|
|
14
19
|
/**
|
|
15
20
|
* Resolves the worktree a tool call may act on. It must be an absolute path that, after
|
|
16
|
-
* resolving symlinks, is exactly the top level of a git worktree
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
21
|
+
* resolving symlinks, is exactly the top level of a git worktree. A subdirectory, a relative or
|
|
22
|
+
* nonexistent path, and a non-git directory are rejected. When the server was launched inside a
|
|
23
|
+
* repository, the path must also be one of that repository's worktrees (primary or linked), so an
|
|
24
|
+
* unrelated `git init` directory is refused. A server launched outside any repository accepts the
|
|
25
|
+
* top level of any worktree. Git is asked on every call, so worktrees created after the server
|
|
26
|
+
* started are accepted and removed ones are not. Without `requested`, the launch worktree is used.
|
|
20
27
|
*/
|
|
21
28
|
export declare function resolveWorktree(input: {
|
|
22
29
|
launchRoot: string | undefined;
|
|
@@ -52,12 +52,33 @@ export async function launchWorktreeRoot(cwd, runGit) {
|
|
|
52
52
|
}
|
|
53
53
|
return realpath(output.trim());
|
|
54
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* The worktree paths in `git worktree list --porcelain -z` output: NUL-terminated fields, an empty
|
|
57
|
+
* field ending each record, so a path containing a newline cannot forge a record.
|
|
58
|
+
*/
|
|
59
|
+
export function parseWorktreeList(output) {
|
|
60
|
+
return output
|
|
61
|
+
.split('\0')
|
|
62
|
+
.filter((field) => field.startsWith('worktree '))
|
|
63
|
+
.map((field) => field.slice('worktree '.length));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The real paths of every worktree of the repository containing `root` (the primary worktree and
|
|
67
|
+
* each linked one).
|
|
68
|
+
*/
|
|
69
|
+
async function repositoryWorktrees(root, runGit) {
|
|
70
|
+
const output = await runGit(['-C', root, 'worktree', 'list', '--porcelain', '-z']);
|
|
71
|
+
const paths = parseWorktreeList(output).map(canonical);
|
|
72
|
+
return new Set((await Promise.all(paths)).filter((path) => path !== undefined));
|
|
73
|
+
}
|
|
55
74
|
/**
|
|
56
75
|
* Resolves the worktree a tool call may act on. It must be an absolute path that, after
|
|
57
|
-
* resolving symlinks, is exactly the top level of a git worktree
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
76
|
+
* resolving symlinks, is exactly the top level of a git worktree. A subdirectory, a relative or
|
|
77
|
+
* nonexistent path, and a non-git directory are rejected. When the server was launched inside a
|
|
78
|
+
* repository, the path must also be one of that repository's worktrees (primary or linked), so an
|
|
79
|
+
* unrelated `git init` directory is refused. A server launched outside any repository accepts the
|
|
80
|
+
* top level of any worktree. Git is asked on every call, so worktrees created after the server
|
|
81
|
+
* started are accepted and removed ones are not. Without `requested`, the launch worktree is used.
|
|
61
82
|
*/
|
|
62
83
|
export async function resolveWorktree(input) {
|
|
63
84
|
const { launchRoot, requested, runGit } = input;
|
|
@@ -68,8 +89,12 @@ export async function resolveWorktree(input) {
|
|
|
68
89
|
if (!isAbsolute(path))
|
|
69
90
|
throw new Error('worktree must be an absolute path to a git worktree');
|
|
70
91
|
const target = await canonical(path);
|
|
71
|
-
if (target !== undefined && (await launchWorktreeRoot(target, runGit)) === target)
|
|
72
|
-
|
|
92
|
+
if (target !== undefined && (await launchWorktreeRoot(target, runGit)) === target) {
|
|
93
|
+
if (launchRoot === undefined || (await repositoryWorktrees(launchRoot, runGit)).has(target))
|
|
94
|
+
return target;
|
|
95
|
+
throw new Error(`worktree ${path} is not a worktree of the repository this server was launched in ` +
|
|
96
|
+
`(${launchRoot}); \`git worktree list\` there names the worktrees it may act on`);
|
|
97
|
+
}
|
|
73
98
|
throw new Error(`worktree ${path} is not the top level of a git worktree: it must exist and be the ` +
|
|
74
99
|
'directory `git rev-parse --show-toplevel` reports, not a subdirectory of it');
|
|
75
100
|
}
|
package/docs/mcp-server.md
CHANGED
|
@@ -6,8 +6,8 @@ a tool argument; there is no temp file, `--file`, or replay command. The server
|
|
|
6
6
|
existing `vouchington-tooling/agent-blackboard` exports and adds no provider protocol.
|
|
7
7
|
|
|
8
8
|
Phase 1 of [vouchington-tooling#324](https://github.com/vouchington/vouchington-tooling/issues/324)
|
|
9
|
-
covers the journal, outbox, session, and snapshot tools below.
|
|
10
|
-
|
|
9
|
+
covers the journal, outbox, session, and snapshot tools below. Phase 2 adds the retrospective tools
|
|
10
|
+
and the policy contract they run. Phase 3 adds the pull request tools.
|
|
11
11
|
|
|
12
12
|
## Install
|
|
13
13
|
|
|
@@ -82,15 +82,17 @@ Every tool takes an explicit `sessionId` (`^[A-Za-z0-9._:-]+$`, at most 256 char
|
|
|
82
82
|
server never infers, and an optional `worktree`. Unknown arguments are rejected. Failures come back
|
|
83
83
|
as MCP tool errors (`isError: true`) with an actionable message rather than crashing the server.
|
|
84
84
|
|
|
85
|
-
| Tool
|
|
86
|
-
|
|
|
87
|
-
| `journal_append`
|
|
88
|
-
| `journal_entries`
|
|
89
|
-
| `outbox_status`
|
|
90
|
-
| `outbox_flush`
|
|
91
|
-
| `session_ensure`
|
|
92
|
-
| `
|
|
93
|
-
| `
|
|
85
|
+
| Tool | Arguments besides `sessionId` and `worktree` | Returns |
|
|
86
|
+
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
87
|
+
| `journal_append` | `parentSessionId`, `agent`, `version`, `mode` (`interactive` or `autonomous`), `markdown`, `sourceEventId`, `workOutcome`, `repositories`, `feedbackCoverage`, optional `category` | `sessionId`, `timestamp`, delivery `status` (`delivered` with a verified receipt, or `pending`), `pendingCount`, `worktreePendingCount` |
|
|
88
|
+
| `journal_entries` | none | `sessionId` and `entries`, every entry oldest first |
|
|
89
|
+
| `outbox_status` | none | `sessionId`, `status`, `pendingCount`, `worktreePendingCount`, `rejectedCount`, `worktreeRejectedCount` |
|
|
90
|
+
| `outbox_flush` | none | `sessionId`, `status`, `pendingCount`, `worktreePendingCount`, `rejectedCount`, `worktreeRejectedCount`, `deliveredCount`, `rejected` (when any) |
|
|
91
|
+
| `session_ensure` | `parentSessionId`, `agent`, `version` | `sessionId`, `status` (`created` or `exists`), `archived` |
|
|
92
|
+
| `retrospective_check` | optional `sourceEventId` | `sessionId`, `saved`, `entries` (`sourceEventId`, `timestamp`, `issues`, `prs` of each retrospective, oldest first) |
|
|
93
|
+
| `retrospective_save` | `parentSessionId`, `agent`, `version`, `mode`, `workOutcome`, `feedbackCoverage`, exactly one of `markdown` or `compose`, optional `repositories`, `sourceEventId`, `category`, `dryRun` | as `journal_append` plus `policy.warnings`; with `dryRun`, `markdown` and `policy.warnings` |
|
|
94
|
+
| `snapshot_export` | optional `agent`, `version`, `parentSessionId`, `data`, `dataArrayContains`, `inactiveForHours` | `sessionId`, `path`, `counts`, `checksum`, `manifest`, `cleanupToken` |
|
|
95
|
+
| `session_archive` | none | `sessionId`, `archived: true` |
|
|
94
96
|
|
|
95
97
|
`journal_append` validates the whole feedback envelope with `validateFeedbackEnvelope` before
|
|
96
98
|
anything is written, so a missing required field writes nothing. It returns the verified read-back
|
|
@@ -163,20 +165,166 @@ is unavailable.
|
|
|
163
165
|
The server sends the same list in its MCP `instructions`, so a harness that surfaces server
|
|
164
166
|
instructions shows it to the agent.
|
|
165
167
|
|
|
168
|
+
## Retrospective tools and the policy contract
|
|
169
|
+
|
|
170
|
+
`retrospective_check` reads the session through the same client as `journal_entries` and reports the
|
|
171
|
+
entries whose envelope `type` is `retrospective`. A session that was never created (the blackboard
|
|
172
|
+
answers 404) reports `saved: false`.
|
|
173
|
+
|
|
174
|
+
`retrospective_save` builds a `retrospective` envelope and writes it exactly as `journal_append`
|
|
175
|
+
does (same receipt, `pendingCount`, `worktreePendingCount`, outbox rules, and server-owned
|
|
176
|
+
timestamp; a `timestamp` argument is rejected). The document comes from `markdown` or from
|
|
177
|
+
`compose`, the input of `composeRetrospective` (`narrative`, `date`, `issues`, `prs`,
|
|
178
|
+
`description`, `facts`, `transcript`, `tools`, `architecture`, optional `knownSensitiveValues`;
|
|
179
|
+
unknown keys are rejected, and the journal audit reads through the server's own client).
|
|
180
|
+
`compose.transcript` accepts only `{ sessionId?, harnessSessionId? }` (`sessionId` must equal the
|
|
181
|
+
call's `sessionId`) or `{ status, reason }` for an unassessed transcript. The transcript is found by
|
|
182
|
+
`harnessSessionId`, the harness's own session UUID, else by the call's `sessionId` when that is a
|
|
183
|
+
UUID; otherwise the call fails rather than reporting the transcript unavailable. When the server's environment names its harness session (`CLAUDE_CODE_SESSION_ID`, `CODEX_THREAD_ID`, and so on), only that session's transcript is read (after `/clear` or a thread switch, reconnect the server, or pass `{ status, reason }`); `jsonlPath`, `projectsDir`, `codexSessionsDir`,
|
|
184
|
+
`grokSessionsDir`, and `cwd` are rejected, and the server reads the transcript from the worktree
|
|
185
|
+
and its default session roots. The server's blackboard token, and any `knownSensitiveValues`, are
|
|
186
|
+
redacted from the document before the policy sees it or the envelope is stored. Facts commands
|
|
187
|
+
are killed after 120 seconds. `compose.facts` takes `pr`, `branch`, `noPr`, or `{ status, reason }`;
|
|
188
|
+
`repo` is rejected, so the server's GitHub token never queries a repository other than the
|
|
189
|
+
worktree's. Facts run `git` and `gh` in the worktree as the server's user, honoring its
|
|
190
|
+
repository configuration (which can select helpers that run code), so the worktree, including
|
|
191
|
+
`.git/config`, must be as trusted as the policy module. `dryRun`
|
|
192
|
+
runs every check and returns the document without writing.
|
|
193
|
+
|
|
194
|
+
The document must open with the front matter `composeRetrospective` writes: lines of
|
|
195
|
+
`key: <JSON value>` between two `---` lines. Recognized keys are `date`, `issues`, `prs` (required),
|
|
196
|
+
`session_id`, `description`, `work_outcome`, `feedback_coverage` (`status`, `sources`,
|
|
197
|
+
`droppedCount`), and `repositories`; anything else, a duplicate, or a malformed line is rejected.
|
|
198
|
+
`session_id`, `work_outcome`, `feedback_coverage`, and `repositories` must equal the arguments when
|
|
199
|
+
present. `repositories` resolves from the argument, else the front matter, else the policy's
|
|
200
|
+
`pullRequestDefaults()` result, else an error, and is stored sorted, deduplicated, and lowercase.
|
|
201
|
+
The default `sourceEventId` is `retrospective-<sessionId>`, or `retrospective-<sha256 hex of the
|
|
202
|
+
sessionId>` when that would exceed 256 bytes. Re-saving identical content returns the
|
|
203
|
+
stored receipt and timestamp. Changed content under the same `sourceEventId` is an
|
|
204
|
+
`event-conflict`; a revision needs a new `sourceEventId`. `compose` regenerates the document from
|
|
205
|
+
the live journal and repository, so repeating a `compose` call is not a retry: when an interactive
|
|
206
|
+
save is retained in the outbox, deliver it with `outbox_flush`.
|
|
207
|
+
|
|
208
|
+
### Policy module
|
|
209
|
+
|
|
210
|
+
The consumer's root `package.json` may declare
|
|
211
|
+
`"vouchington-tooling": { "policy": "dev/agent-policy.mts" }`. The path must be relative and, after
|
|
212
|
+
resolving symlinks, stay inside the worktree. Without a declaration the policy checks are skipped.
|
|
213
|
+
Policy tools also require a server launched inside a repository worktree on macOS or Linux (they
|
|
214
|
+
refuse to run on Windows, where the server cannot reap a policy's descendants by process group),
|
|
215
|
+
and refuse to run when `AGENT_BLACKBOARD_TOKEN`, `GH_TOKEN`, or `GITHUB_TOKEN` is set to a value
|
|
216
|
+
shorter than four characters, which could not be redacted from policy output. Each call runs one named
|
|
217
|
+
export in a fresh child process (`bin/policy-runner.mjs`, started with the server's own Node), never
|
|
218
|
+
inside the server, with the worktree as its working directory and an environment of only `HOME`,
|
|
219
|
+
`PATH` (`/usr/bin`, `/bin`, `/usr/local/bin`, `/opt/homebrew/bin`, the Node directory, and the
|
|
220
|
+
directory of the first `gh` on the server's `PATH`, deduplicated), and `GH_TOKEN` / `GITHUB_TOKEN` when the server has
|
|
221
|
+
them. Blackboard credentials and `NODE_OPTIONS` are not passed. The child still runs as the
|
|
222
|
+
server's user, so this is not a sandbox: on Linux it could read the server's environment from
|
|
223
|
+
`/proc/<server pid>/environ`. Treat the policy module as trusted code. After the runner exits, its
|
|
224
|
+
whole process group is killed, so nothing it started outlives the call. The runner reads
|
|
225
|
+
`{ policyVersion: 1, export, input }` on stdin and writes `{ ok: true, result }` (or
|
|
226
|
+
`{ ok: false, error }`) on stdout; a missing export is skipped.
|
|
227
|
+
|
|
228
|
+
| Export | Input | Result | Timeout |
|
|
229
|
+
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- | ------- |
|
|
230
|
+
| `validateRetrospective` | `sessionId`, `parentSessionId`, `mode`, `workOutcome`, `feedbackCoverage`, `repositories`, `markdown`, `sourceEventId`, `metadata` (`date`, `issues`, `prs`, `description`) | `{ errors: string[], warnings?: string[] }` | 30 s |
|
|
231
|
+
| `pullRequestDefaults` | `{}` from `retrospective_save`; `{ title, base, relatedIssues }` from `pr_create` | `{ repositories?, labels?, milestone?, project? }` (an object) | 120 s |
|
|
232
|
+
| `validatePullRequest` | `title`, `body`, `base`, `head`, `mode`, and `pr` for an update | `{ errors: string[], warnings?: string[] }` | 120 s |
|
|
233
|
+
|
|
234
|
+
Any `errors` reject the save and write nothing. A non-zero exit, a timeout (reported as
|
|
235
|
+
`policy <export> timed out after <N>s`), output over 1 MB, malformed JSON, or a result of the wrong
|
|
236
|
+
shape is an error, never a pass.
|
|
237
|
+
|
|
238
|
+
## Pull request tools
|
|
239
|
+
|
|
240
|
+
`vouchington mcp` serves `pr_validate`, `pr_create` and `pr_update` besides the journal and
|
|
241
|
+
retrospective tools. All three take `sessionId` (required) and `worktree` (optional), need a server
|
|
242
|
+
launched inside a repository worktree (they run the checkout policy), and share this structured
|
|
243
|
+
input: `title` (single line, at most 256 characters), `summary`, `impact`, `relatedIssues`
|
|
244
|
+
(`[{ number, relation: "closes" | "refs", reason }]`, may be empty), `details` (string array),
|
|
245
|
+
`base` (default `main`), `agent`, optional `model`, `mode` (`interactive` | `autonomous`) and
|
|
246
|
+
`workspaceSetup`. `pr_update` also requires `pr`; `pr_validate` accepts an optional `pr`.
|
|
247
|
+
|
|
248
|
+
### Rendering
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
## Summary
|
|
252
|
+
|
|
253
|
+
<summary>
|
|
254
|
+
|
|
255
|
+
## Impact
|
|
256
|
+
|
|
257
|
+
<impact>
|
|
258
|
+
|
|
259
|
+
## Related issues
|
|
260
|
+
|
|
261
|
+
- Closes #N — <reason> (or "Refs #N"; "- None" when empty)
|
|
262
|
+
|
|
263
|
+
## Details
|
|
264
|
+
|
|
265
|
+
<details joined by blank lines>
|
|
266
|
+
|
|
267
|
+
Workspace setup: <workspaceSetup>
|
|
268
|
+
Agent: <agent>[ (<model>)] session <sessionId>
|
|
269
|
+
Device: <os user>@<lowercased hostname>
|
|
270
|
+
Worktree: <path after "/worktrees/", "main" for the main worktree, else the directory name>
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
The head branch is the worktree's current branch; a detached HEAD, or a head equal to `base`, is
|
|
274
|
+
rejected. The body must stay within GitHub's 65536 characters. `pr_create` sees no stack membership,
|
|
275
|
+
so a consumer policy may reject any `base` other than the default branch; create stacked layers
|
|
276
|
+
with the repository's stack tooling instead.
|
|
277
|
+
|
|
278
|
+
### Tools
|
|
279
|
+
|
|
280
|
+
- `pr_validate` (read-only): renders the body, runs the policy `validatePullRequest({ title, body,
|
|
281
|
+
base, head, mode, pr? })` export and returns `{ body, errors, warnings }`. With `pr`, the live
|
|
282
|
+
body is read (never written) so its Shepherd Journal is preserved as in `pr_update`.
|
|
283
|
+
- `pr_create` (additive): renders and validates (errors block), then calls the policy
|
|
284
|
+
`pullRequestDefaults({ title, base, relatedIssues })` export for optional `labels`, `milestone`
|
|
285
|
+
and `project`. It checks the head is pushed and current, writes the body to a mode 0600 temp file
|
|
286
|
+
(removed afterwards) and runs `gh pr create --draft` with the labels, `--milestone` and
|
|
287
|
+
`--project`. Returns `{ number, url, warnings }`.
|
|
288
|
+
- `pr_update` (mutating): reads `gh pr view <pr> --json body,headRefName,baseRefName,state`,
|
|
289
|
+
rejects a pull request that is not `OPEN` or whose head is not the worktree branch, and validates
|
|
290
|
+
against the live base (an explicit `base` must equal it). It renders, appends the live Shepherd
|
|
291
|
+
Journal (a `<details><summary>Shepherd Journal</summary>` block, or a legacy `## Shepherd Journal`
|
|
292
|
+
section to the next H2 or the end; examples in fenced code blocks are ignored), validates with
|
|
293
|
+
`pr` set (errors block), then runs `gh pr edit <pr> --title --body-file`. More than one journal
|
|
294
|
+
in the live body, one in the rendered fields, an unclosed journal, or rendered fields that leave
|
|
295
|
+
a code fence open are rejected rather than guessed. Just before the edit the live body is read
|
|
296
|
+
again, and the update aborts if its journal changed meanwhile. Nothing else is carried over from
|
|
297
|
+
the live body. Returns `{ number, url, warnings }`.
|
|
298
|
+
|
|
299
|
+
Every field is redacted of `AGENT_BLACKBOARD_TOKEN`, `GH_TOKEN`, and `GITHUB_TOKEN` before
|
|
300
|
+
rendering, the policy, or `gh`; a worktree branch name containing one is rejected. `title` and
|
|
301
|
+
`workspaceSetup` must be single lines, and the title is limited to 256 characters after redaction.
|
|
302
|
+
|
|
303
|
+
### gh access
|
|
304
|
+
|
|
305
|
+
`gh` is found in the absolute directories of the server's `PATH` (relative entries are skipped,
|
|
306
|
+
since they would resolve inside the target worktree), else at `/opt/homebrew/bin/gh`, `/usr/local/bin/gh` or
|
|
307
|
+
`/usr/bin/gh`; otherwise the tool fails with "gh CLI not found on PATH; install gh or add it to the
|
|
308
|
+
server's PATH". It runs with the worktree as its working directory and the server environment
|
|
309
|
+
without `AGENT_BLACKBOARD_URL`, `AGENT_BLACKBOARD_TOKEN`, and `GH_REPO` (which would point it at
|
|
310
|
+
another repository), and each call is killed after 120 seconds.
|
|
311
|
+
|
|
166
312
|
## Worktree validation
|
|
167
313
|
|
|
168
314
|
`worktree` is checked on every call. It must be an absolute path that, after resolving symlinks,
|
|
169
315
|
equals the top level of a git worktree, that is, what `git rev-parse --show-toplevel` prints when
|
|
170
|
-
run in it.
|
|
171
|
-
the
|
|
172
|
-
|
|
316
|
+
run in it. When the server was launched inside a repository, the real path must also equal one of
|
|
317
|
+
the `worktree` entries of `git worktree list --porcelain` run in the launch root (the primary
|
|
318
|
+
worktree or a linked one), so an unrelated repository is refused. A server launched outside any
|
|
319
|
+
repository accepts the top level of any git worktree for the journal tools, but refuses the policy
|
|
320
|
+
tools. Worktrees created after launch are accepted and removed ones are refused. Rejected: a subdirectory of a worktree, a
|
|
173
321
|
relative or nonexistent path, and a directory that is not in a git worktree. Git runs without
|
|
174
322
|
`GIT_*` variables, so `GIT_DIR` cannot redirect the check, and with `LC_ALL=C` and no `LANGUAGE`, so
|
|
175
323
|
its "not a git repository" diagnostic is never translated.
|
|
176
324
|
|
|
177
325
|
`worktree` only chooses where the outbox lives. It is not a trust boundary for loading code: the
|
|
178
|
-
server never imports modules from it
|
|
179
|
-
|
|
326
|
+
server never imports modules from it. The policy tools run the declared module only in a child
|
|
327
|
+
process without blackboard credentials, as described above.
|
|
180
328
|
|
|
181
329
|
When `worktree` is omitted, the launch directory's worktree top level is used if the launch
|
|
182
330
|
directory is inside one, and it is validated like any explicit path. If the server was launched
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vouchington-tooling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.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": {
|
|
@@ -54,6 +54,14 @@ not evidence of a miss; classify genuine CI-only or external infrastructure fail
|
|
|
54
54
|
Retain confirmed gap records after CI turns green. Diagnose the failure through the repository's
|
|
55
55
|
CI investigation workflow; a failed status alone does not establish a gap.
|
|
56
56
|
|
|
57
|
+
Create, update, and validate pull requests through the `pr_create`, `pr_update`, and `pr_validate`
|
|
58
|
+
MCP tools with structured input, not by hand-writing a body: pass `title`, `summary`, `impact`,
|
|
59
|
+
`relatedIssues` (`closes` or `refs`, each with a reason), `details` (Markdown paragraphs or
|
|
60
|
+
bullets), `base`, `agent`, optional `model`, `mode`, and `workspaceSetup`. The server renders the
|
|
61
|
+
required headings and provenance lines, runs the repository's policy validation, and blocks on its
|
|
62
|
+
errors, so put the guidance above into `summary`, `impact`, and `details`. Run `pr_validate` first
|
|
63
|
+
to preview the rendered body. `pr_update` preserves the live Shepherd Journal.
|
|
64
|
+
|
|
57
65
|
Use the repository's approved PR tooling and preserve sections managed by its review automation
|
|
58
66
|
verbatim. Close only issues fully resolved and explain non-closing issue links. Apply this guidance
|
|
59
67
|
to descriptions being created or updated, not a bulk rewrite of historical pull requests.
|
|
@@ -14,7 +14,8 @@ local `AGENTS.md`, and journal guidance first.
|
|
|
14
14
|
|
|
15
15
|
1. Read the agent's own journal oldest-first and check for an existing retrospective. Capture a
|
|
16
16
|
material new observation before filing a follow-up issue. Preserve entry-level repository tags.
|
|
17
|
-
2. Generate routine repository and transcript facts with the approved composer
|
|
17
|
+
2. Generate routine repository and transcript facts with the approved composer: pass `compose` to
|
|
18
|
+
the `retrospective_save` MCP tool and preview the composition with `dryRun`, never a CLI. Keep exact factual
|
|
18
19
|
section markers generated, preserve unavailable counts, and never replace missing evidence with
|
|
19
20
|
invented zero statistics. Do not paste raw collectors' output or manually reconstruct facts.
|
|
20
21
|
3. Record work outcome, feedback coverage, and delivery independently. Success, failure,
|
|
@@ -30,7 +31,9 @@ local `AGENTS.md`, and journal guidance first.
|
|
|
30
31
|
5. Keep the top five findings or improvements in the human prose. The bounded durable evidence can
|
|
31
32
|
retain additional relevant findings; a prose limit is not permission to silently discard them.
|
|
32
33
|
Separate recurring root causes from one-off noise and preserve existing issue references.
|
|
33
|
-
6.
|
|
34
|
+
6. Check for an existing retrospective with the `retrospective_check` MCP tool and save with
|
|
35
|
+
`retrospective_save`, never a CLI. A revision of changed content needs a new `sourceEventId`.
|
|
36
|
+
Interactive outages use the approved bounded private
|
|
34
37
|
outbox with visible pending count; failed persistence blocks capture. Autonomous reporting stays
|
|
35
38
|
online and never uses that outbox to authorize an attempt or completion. Report the acknowledged
|
|
36
39
|
record/receipt, or the actual pending/blocked state and next replay action.
|