vouchington-tooling 0.47.0 → 0.48.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 CHANGED
@@ -165,7 +165,11 @@ pass `--root`, `--workflow-directory`, or `--action-directory` for consumer-owne
165
165
  Claude-compatible transcript when `CURSOR_SESSION_ID` is set, and Grok's `updates.jsonl` session
166
166
  layout when `GROK_SESSION_ID` is set. Use `--grok-sessions-dir` to point discovery at a nondefault
167
167
  Grok session root. Without `--session-id`, it reads those session identities from the host
168
- environment.
168
+ environment. `resolveTranscriptFile` is the canonical resolver: it lowercases and UUID-validates
169
+ session ids, honors `CODEX_HOME`, `CLAUDE_CONFIG_DIR` and `GROK_HOME`, and errors when more than one
170
+ transcript matches. The subpath also exports `normalizeCommandPrefix`, `splitCommandSegments`,
171
+ `isPackageRunner`, `asRecord`, `asArray` and `openTranscriptLines(path, { maxRecordBytes })`, which
172
+ streams lines (2 MiB per record by default) and throws from the iterable on an oversized record.
169
173
 
170
174
  `retrospective-facts` keeps local Git evidence separate from GitHub PR data. `Commits ahead of
171
175
  origin/main` is populated only from a local ancestry range; GitHub responses instead populate
@@ -1,26 +1,4 @@
1
- export declare const HARNESS_IDS: readonly ["claude", "codex", "grok", "cursor"];
2
- export type HarnessId = (typeof HARNESS_IDS)[number];
3
- export interface HarnessEnvironment {
4
- readonly claude: {
5
- readonly compatMode: boolean;
6
- readonly sessionId?: string;
7
- };
8
- readonly codex: {
9
- readonly sessionId?: string;
10
- };
11
- readonly cursor: {
12
- readonly agentMarker: boolean;
13
- readonly sessionId?: string;
14
- };
15
- readonly grok: {
16
- readonly agentMarker: boolean;
17
- readonly hookEvent: boolean;
18
- readonly sessionId?: string;
19
- };
20
- }
21
- export interface HarnessSessionIdentity {
22
- readonly harness: HarnessId;
23
- readonly sessionId: string;
24
- }
1
+ import { type HarnessEnvironment } from 'agent-blackboard';
2
+ export { HARNESS_IDS, selectHarnessSession } from 'agent-blackboard';
3
+ export type { HarnessEnvironment, HarnessId, HarnessSessionIdentity } from 'agent-blackboard';
25
4
  export declare function inspectHarnessEnvironment(env?: NodeJS.ProcessEnv): HarnessEnvironment;
26
- export declare function selectHarnessSession(environment: HarnessEnvironment, precedence: readonly HarnessId[]): HarnessSessionIdentity | undefined;
@@ -1,30 +1,5 @@
1
- export const HARNESS_IDS = ['claude', 'codex', 'grok', 'cursor'];
2
- function session(value) {
3
- return value ? { sessionId: value } : {};
4
- }
1
+ import { inspectHarnessEnvironment as inspect } from 'agent-blackboard';
2
+ export { HARNESS_IDS, selectHarnessSession } from 'agent-blackboard';
5
3
  export function inspectHarnessEnvironment(env = process.env) {
6
- return {
7
- claude: {
8
- compatMode: env.CLAUDECODE === '1',
9
- ...session(env.CLAUDE_CODE_SESSION_ID),
10
- },
11
- codex: session(env.CODEX_THREAD_ID),
12
- cursor: {
13
- agentMarker: Boolean(env.CURSOR_AGENT),
14
- ...session(env.CURSOR_SESSION_ID),
15
- },
16
- grok: {
17
- agentMarker: Boolean(env.GROK_AGENT),
18
- hookEvent: Boolean(env.GROK_HOOK_EVENT),
19
- ...session(env.GROK_SESSION_ID),
20
- },
21
- };
22
- }
23
- export function selectHarnessSession(environment, precedence) {
24
- for (const harness of precedence) {
25
- const sessionId = environment[harness].sessionId;
26
- if (sessionId)
27
- return { harness, sessionId };
28
- }
29
- return undefined;
4
+ return inspect(env);
30
5
  }
@@ -1,5 +1,5 @@
1
+ import { HARNESS_SESSION_PRECEDENCE } from 'agent-blackboard';
1
2
  import { inspectHarnessEnvironment } from "../agent-harness-identity/index.mjs";
2
- const PRECEDENCE = ['codex', 'claude', 'cursor', 'grok'];
3
3
  /**
4
4
  * The harness that launched this server, from the launch environment first and the MCP client's
5
5
  * `initialize` `clientInfo.name` second. Neither comes from a tool argument.
@@ -12,11 +12,11 @@ export function deriveAgent(env, clientName) {
12
12
  cursor: found.cursor.agentMarker || Boolean(found.cursor.sessionId),
13
13
  grok: found.grok.agentMarker || found.grok.hookEvent || Boolean(found.grok.sessionId),
14
14
  };
15
- const fromEnv = PRECEDENCE.find((id) => present[id]);
15
+ const fromEnv = HARNESS_SESSION_PRECEDENCE.find((id) => present[id]);
16
16
  if (fromEnv !== undefined)
17
17
  return fromEnv;
18
18
  const name = clientName?.toLowerCase() ?? '';
19
- return PRECEDENCE.find((id) => name.includes(id));
19
+ return HARNESS_SESSION_PRECEDENCE.find((id) => name.includes(id));
20
20
  }
21
21
  /**
22
22
  * The text of the `Agent:` line before the session. The agent is the derived harness; a caller
@@ -0,0 +1 @@
1
+ export declare function asArray(value: unknown): unknown[];
@@ -0,0 +1,3 @@
1
+ export function asArray(value) {
2
+ return Array.isArray(value) ? value : [];
3
+ }
@@ -0,0 +1 @@
1
+ export declare function normalizeCommandPrefix(command: string): string;
@@ -0,0 +1,124 @@
1
+ import { isPackageRunner, splitCommandSegments } from "./command-segments.mjs";
2
+ // A real command/subcommand token (binary name, git subcommand, short flag, script
3
+ // name) is always well under this length. A token that exceeds it isn't a command
4
+ // token at all — it's payload that got tokenized as one word because it had no
5
+ // internal whitespace (a `VAR={...json...}` assignment, a giant quoted printf arg).
6
+ // Such tokens are replaced with a redaction marker rather than truncated: truncation
7
+ // still leaks a prefix of whatever the token contains (a secret can sit at the front,
8
+ // e.g. "AWS_SECRET_ACCESS_KEY=AKIA..."), while a full-token redaction leaks nothing
9
+ // regardless of where sensitive content falls inside it. This intentionally also
10
+ // redacts long-but-benign tokens (e.g. a `cd /very/long/worktree/path` argument) —
11
+ // for a privacy boundary, over-redacting a legitimate token is the safe failure mode.
12
+ const MAX_COMMAND_TOKEN_LENGTH = 40;
13
+ const REDACTED_TOKEN = '…';
14
+ function redactOverlongToken(token) {
15
+ return token.length > MAX_COMMAND_TOKEN_LENGTH ? REDACTED_TOKEN : token;
16
+ }
17
+ const ENV_ASSIGNMENT_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*=/;
18
+ // A segment's real command starts after any leading `VAR=value` assignments (e.g.
19
+ // "CI=1 pnpm exec vitest", "GIT_EDITOR=true git rebase --continue" — both real
20
+ // patterns used in real agent sessions). Without stripping these, the
21
+ // env-var token itself gets normalized as if it were the command, so "CI=1 pnpm exec
22
+ // vitest" would classify as prefix "CI=1 pnpm" instead of "pnpm exec vitest". Only
23
+ // strips when a token remains afterward — a segment that is *entirely*
24
+ // assignment-shaped tokens (e.g. a lone `OLD_POLICY_JSON={...}` token with no
25
+ // whitespace) has no command to recover, and is left alone so it still falls through
26
+ // to the general redaction path below instead of normalizing to an empty prefix.
27
+ function stripEnvPrefix(segment) {
28
+ const index = segment.findIndex((token) => !ENV_ASSIGNMENT_PATTERN.test(token));
29
+ return index === -1 ? segment : segment.slice(index);
30
+ }
31
+ // Git global options precede the real subcommand — "git -C <path> status" and
32
+ // "git status" are the same command family for policy-matching purposes (`git -h`
33
+ // documents `git [-C <path>] [-c <name>=<value>] ... <command> [<args>]`). `-C`/`-c`
34
+ // take a separate-token argument; `--git-dir=<path>`-style options fold their argument
35
+ // into one `=`-joined token; the rest take no argument at all. Skipping them keeps the
36
+ // normalized prefix on the subcommand instead of stalling on the option — without this,
37
+ // e.g. `git -C <worktree> status` normalizes to "git -C" and reports as an uncovered
38
+ // escalation/block candidate under the wrong prefix, even though `git status` is
39
+ // already covered by an existing narrow allow rule.
40
+ const GIT_GLOBAL_OPTIONS_WITH_ARG = new Set(['-C', '-c']);
41
+ const GIT_GLOBAL_OPTIONS_NO_ARG = new Set([
42
+ '-v',
43
+ '--version',
44
+ '-h',
45
+ '--help',
46
+ '--exec-path',
47
+ '--html-path',
48
+ '--man-path',
49
+ '--info-path',
50
+ '-p',
51
+ '--paginate',
52
+ '-P',
53
+ '--no-pager',
54
+ '--no-replace-objects',
55
+ '--bare',
56
+ ]);
57
+ const GIT_GLOBAL_OPTION_WITH_INLINE_ARG = /^--[a-z-]+=/;
58
+ function stripGitGlobalOptions(tokens) {
59
+ let index = 0;
60
+ while (index < tokens.length) {
61
+ const token = tokens[index];
62
+ if (GIT_GLOBAL_OPTIONS_WITH_ARG.has(token))
63
+ index += 2;
64
+ else if (GIT_GLOBAL_OPTIONS_NO_ARG.has(token) || GIT_GLOBAL_OPTION_WITH_INLINE_ARG.test(token))
65
+ index += 1;
66
+ else
67
+ break;
68
+ }
69
+ return tokens.slice(index);
70
+ }
71
+ // Normalizes a segment to "<leading token> <first subcommand>" (e.g. "git status",
72
+ // "gh pr", or just "cat" when there's no second token), with one exception that goes
73
+ // one token deeper because a permissions file may distinguish at that depth:
74
+ // package runners — "pnpm run build" vs "pnpm exec build" are different
75
+ // permission entries, so the run/exec target must
76
+ // stay out of the generic two-token rule.
77
+ function normalizeSegment(segment) {
78
+ const [first, ...rest] = segment;
79
+ if (!first)
80
+ return '';
81
+ // A segment that is only assignments (stripEnvPrefix left it intact) has no command;
82
+ // its value may be a secret of any length, so always redact it.
83
+ if (ENV_ASSIGNMENT_PATTERN.test(first))
84
+ return REDACTED_TOKEN;
85
+ if (isPackageRunner(first) && (rest[0] === 'run' || rest[0] === 'exec') && rest[1]) {
86
+ return `${redactOverlongToken(first)} ${rest[0]} ${redactOverlongToken(rest[1])}`;
87
+ }
88
+ const effectiveRest = first === 'git' ? stripGitGlobalOptions(rest) : rest;
89
+ return effectiveRest[0]
90
+ ? `${redactOverlongToken(first)} ${redactOverlongToken(effectiveRest[0])}`
91
+ : redactOverlongToken(first);
92
+ }
93
+ // A leading "cd <path> && ..." (or "cd <path>; ...") prelude is not itself the
94
+ // actionable command — it's a workdir change common in Bash sessions with no separate
95
+ // cwd param. Without skipping it, "cd /repo && pnpm install" would normalize to "cd
96
+ // /repo" and both miss existing pnpm policy coverage and leak a checkout path into
97
+ // non-raw retro output. Only a *leading run* of cd segments is skipped — a bare `cd
98
+ // /repo` with nothing after it has no other segment to fall back to, so it still
99
+ // normalizes as-is.
100
+ function skipLeadingCdSegments(segments) {
101
+ let index = 0;
102
+ while (index < segments.length - 1 && stripEnvPrefix(segments[index])[0] === 'cd')
103
+ index++;
104
+ return segments.slice(index);
105
+ }
106
+ // Produces a classification key for a raw shell command, for comparison against the
107
+ // prefix lists in a consumer permissions file (excluded commands, allow,
108
+ // and deny lists). This is a privacy
109
+ // boundary: callers building non---raw report output must emit only this normalized
110
+ // prefix, never the raw `command` string.
111
+ //
112
+ // Only the first non-cd segment of a compound command (`git status && git push`) is
113
+ // normalized — this is a rough per-command categorization tool, not a full command
114
+ // parser, so a compound command is bucketed by its first actionable part.
115
+ //
116
+ // The leading token is preserved as-is (no basename stripping for a path-qualified
117
+ // binary like "/usr/bin/git"):
118
+ // permissions files may also list literal relative-path scripts (e.g. "./scripts/run")
119
+ // that must not be collapsed to a basename.
120
+ export function normalizeCommandPrefix(command) {
121
+ const segments = skipLeadingCdSegments(splitCommandSegments(command));
122
+ const segment = stripEnvPrefix(segments[0] ?? []);
123
+ return normalizeSegment(segment);
124
+ }
@@ -0,0 +1,2 @@
1
+ export declare function splitCommandSegments(command: string): string[][];
2
+ export declare function isPackageRunner(word: string | undefined): boolean;
@@ -0,0 +1,54 @@
1
+ // Commands are matched by command position, not by scanning for words anywhere in
2
+ // the string. The string is split into segments on unquoted `;`, `&`, `|` and
3
+ // newlines (so `git push;gh pr view` splits even with no surrounding whitespace).
4
+ // Quoted spans, including any whitespace inside them, are grouped into a single
5
+ // word, mirroring real shell tokenization.
6
+ export function splitCommandSegments(command) {
7
+ const segments = [];
8
+ let current = [];
9
+ let word = '';
10
+ let quote = null;
11
+ const flushWord = () => {
12
+ if (word) {
13
+ current.push(word);
14
+ word = '';
15
+ }
16
+ };
17
+ const flushSegment = () => {
18
+ flushWord();
19
+ if (current.length) {
20
+ segments.push(current);
21
+ current = [];
22
+ }
23
+ };
24
+ for (const char of command) {
25
+ if (quote) {
26
+ if (char === quote)
27
+ quote = null;
28
+ else
29
+ word += char;
30
+ continue;
31
+ }
32
+ if (char === '"' || char === "'") {
33
+ quote = char;
34
+ continue;
35
+ }
36
+ if (char === ';' || char === '&' || char === '|' || char === '\n' || char === '\r') {
37
+ flushSegment();
38
+ continue;
39
+ }
40
+ if (/\s/.test(char)) {
41
+ flushWord();
42
+ continue;
43
+ }
44
+ word += char;
45
+ }
46
+ flushSegment();
47
+ return segments;
48
+ }
49
+ const PACKAGE_RUNNERS = ['npx', 'pnpm', 'pnpx', 'yarn'];
50
+ export function isPackageRunner(word) {
51
+ if (!word)
52
+ return false;
53
+ return PACKAGE_RUNNERS.some((runner) => word === runner || word.endsWith(`/${runner}`));
54
+ }
@@ -5,6 +5,11 @@ export { codexChildren, codexIdentity } from './codex.mts';
5
5
  export { formatTranscriptFacts, formatUnavailable } from './format.mts';
6
6
  export type { ResolveOptions } from './resolve-transcript-file.mts';
7
7
  export { resolveTranscriptFile } from './resolve-transcript-file.mts';
8
+ export { asRecord } from './shared.mts';
9
+ export { asArray } from './as-array.mts';
10
+ export { normalizeCommandPrefix } from './command-prefix.mts';
11
+ export { isPackageRunner, splitCommandSegments } from './command-segments.mts';
12
+ export { DEFAULT_MAX_RECORD_BYTES, openTranscriptLines } from './transcript-lines.mts';
8
13
  export type RetrospectiveTranscriptReport = {
9
14
  markdown: string;
10
15
  coverage: 'complete' | 'partial' | 'unavailable';
@@ -11,6 +11,11 @@ import { emptyFacts, hasMalformedInteriorRecord, parseLines, } from "./shared.mj
11
11
  export { codexChildren, codexIdentity } from "./codex.mjs";
12
12
  export { formatTranscriptFacts, formatUnavailable } from "./format.mjs";
13
13
  export { resolveTranscriptFile } from "./resolve-transcript-file.mjs";
14
+ export { asRecord } from "./shared.mjs";
15
+ export { asArray } from "./as-array.mjs";
16
+ export { normalizeCommandPrefix } from "./command-prefix.mjs";
17
+ export { isPackageRunner, splitCommandSegments } from "./command-segments.mjs";
18
+ export { DEFAULT_MAX_RECORD_BYTES, openTranscriptLines } from "./transcript-lines.mjs";
14
19
  const MALFORMED_INTERIOR = 'malformed interior transcript record';
15
20
  function partialCapture(lines) {
16
21
  return parseLines(lines).length !== lines.filter((line) => line.trim()).length;
@@ -15,5 +15,15 @@ type TranscriptResolution = {
15
15
  error: string;
16
16
  };
17
17
  export declare function globFrom(root: string, pattern: string): string[];
18
+ /**
19
+ * Canonical transcript resolution for consumers.
20
+ *
21
+ * - Session ids are validated as UUIDs and lowercased (`invalid session id format` otherwise).
22
+ * - Roots honor `CODEX_HOME`, `CLAUDE_CONFIG_DIR` and `GROK_HOME` from `options.env`
23
+ * (default `process.env`), unless an explicit `*SessionsDir`/`projectsDir` is given.
24
+ * - Returns `{ error }` when more than one transcript matches a session, rather than
25
+ * picking the first match, and when none matches.
26
+ * - An explicit `jsonlPath` skips discovery.
27
+ */
18
28
  export declare function resolveTranscriptFile(options: ResolveOptions): TranscriptResolution;
19
29
  export {};
@@ -7,6 +7,16 @@ export const SESSION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9
7
7
  export function globFrom(root, pattern) {
8
8
  return globSync(pattern, { cwd: root }).map((path) => join(root, path));
9
9
  }
10
+ /**
11
+ * Canonical transcript resolution for consumers.
12
+ *
13
+ * - Session ids are validated as UUIDs and lowercased (`invalid session id format` otherwise).
14
+ * - Roots honor `CODEX_HOME`, `CLAUDE_CONFIG_DIR` and `GROK_HOME` from `options.env`
15
+ * (default `process.env`), unless an explicit `*SessionsDir`/`projectsDir` is given.
16
+ * - Returns `{ error }` when more than one transcript matches a session, rather than
17
+ * picking the first match, and when none matches.
18
+ * - An explicit `jsonlPath` skips discovery.
19
+ */
10
20
  export function resolveTranscriptFile(options) {
11
21
  if (options.sessionId && !SESSION_ID.test(options.sessionId))
12
22
  return { error: 'invalid session id format' };
@@ -0,0 +1,22 @@
1
+ export declare const DEFAULT_MAX_RECORD_BYTES: number;
2
+ export type OpenTranscriptLinesOptions = {
3
+ /** Largest allowed record (one line, excluding its newline) in bytes. Defaults to 2 MiB. */
4
+ maxRecordBytes?: number;
5
+ };
6
+ export type OpenTranscriptLinesResult = {
7
+ lines: AsyncIterable<string>;
8
+ } | {
9
+ error: string;
10
+ };
11
+ /**
12
+ * Opens a transcript for streaming, one line at a time, with `String.split('\n')`
13
+ * semantics (a trailing newline yields a final empty line, `\r` is preserved).
14
+ *
15
+ * A `maxRecordBytes` that is not a non-negative safe integer rejects with `RangeError`.
16
+ * An open failure resolves to `{ error }`. An oversized record is NOT skipped or
17
+ * reported inline: the returned async iterable throws
18
+ * `transcript record exceeds <maxRecordBytes> byte limit` when it reaches the
19
+ * record, ending iteration, and the underlying stream is destroyed. Lines yielded
20
+ * before the oversized record remain valid.
21
+ */
22
+ export declare function openTranscriptLines(path: string, { maxRecordBytes }?: OpenTranscriptLinesOptions): Promise<OpenTranscriptLinesResult>;
@@ -0,0 +1,74 @@
1
+ import { createReadStream } from 'node:fs';
2
+ export const DEFAULT_MAX_RECORD_BYTES = 2 * 1024 * 1024;
3
+ /**
4
+ * Opens a transcript for streaming, one line at a time, with `String.split('\n')`
5
+ * semantics (a trailing newline yields a final empty line, `\r` is preserved).
6
+ *
7
+ * A `maxRecordBytes` that is not a non-negative safe integer rejects with `RangeError`.
8
+ * An open failure resolves to `{ error }`. An oversized record is NOT skipped or
9
+ * reported inline: the returned async iterable throws
10
+ * `transcript record exceeds <maxRecordBytes> byte limit` when it reaches the
11
+ * record, ending iteration, and the underlying stream is destroyed. Lines yielded
12
+ * before the oversized record remain valid.
13
+ */
14
+ export async function openTranscriptLines(path, { maxRecordBytes = DEFAULT_MAX_RECORD_BYTES } = {}) {
15
+ if (!Number.isSafeInteger(maxRecordBytes) || maxRecordBytes < 0)
16
+ throw new RangeError('maxRecordBytes must be a non-negative integer');
17
+ const stream = createReadStream(path);
18
+ try {
19
+ await new Promise((resolve, reject) => {
20
+ stream.once('open', () => resolve());
21
+ stream.once('error', reject);
22
+ });
23
+ const lines = streamTranscriptLines(stream, maxRecordBytes);
24
+ const release = lines.return.bind(lines);
25
+ // The file is already open, so cancelling before the first next() must still close it;
26
+ // an unstarted generator never reaches its own finally block.
27
+ lines.return = (value) => {
28
+ stream.destroy();
29
+ return release(value);
30
+ };
31
+ return { lines };
32
+ }
33
+ catch (err) {
34
+ stream.destroy();
35
+ return { error: err.message };
36
+ }
37
+ }
38
+ async function* streamTranscriptLines(stream, maxRecordBytes) {
39
+ const pending = [];
40
+ let pendingBytes = 0;
41
+ const tooLarge = () => new Error(`transcript record exceeds ${maxRecordBytes} byte limit`);
42
+ const line = (tail) => {
43
+ const bytes = pendingBytes + (tail?.byteLength ?? 0);
44
+ if (bytes > maxRecordBytes)
45
+ throw tooLarge();
46
+ const value = Buffer.concat(tail ? [...pending, tail] : pending, bytes).toString('utf8');
47
+ pending.length = 0;
48
+ pendingBytes = 0;
49
+ return value;
50
+ };
51
+ try {
52
+ for await (const chunk of stream) {
53
+ let start = 0;
54
+ for (;;) {
55
+ const newline = chunk.indexOf(10, start);
56
+ if (newline === -1)
57
+ break;
58
+ yield line(chunk.subarray(start, newline));
59
+ start = newline + 1;
60
+ }
61
+ if (start < chunk.byteLength) {
62
+ const tail = chunk.subarray(start);
63
+ pendingBytes += tail.byteLength;
64
+ if (pendingBytes > maxRecordBytes)
65
+ throw tooLarge();
66
+ pending.push(tail);
67
+ }
68
+ }
69
+ yield line();
70
+ }
71
+ finally {
72
+ stream.destroy();
73
+ }
74
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vouchington-tooling",
3
- "version": "0.47.0",
3
+ "version": "0.48.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": {
@@ -375,6 +375,7 @@
375
375
  "@types/mdast": "4.0.4",
376
376
  "@types/unist": "3.0.3",
377
377
  "@typescript-eslint/parser": "^8.71.1",
378
+ "agent-blackboard": "^0.7.0",
378
379
  "csv-parse": "7.0.3",
379
380
  "csv-stringify": "6.9.0",
380
381
  "dockerfile-ast": "0.7.1",
@@ -390,22 +391,17 @@
390
391
  "@modelcontextprotocol/sdk": "1.32.1",
391
392
  "@types/picomatch": "^4.0.3",
392
393
  "@types/spdx-expression-parse": "4.0.0",
393
- "agent-blackboard": "^0.6.1",
394
394
  "no-mistakes": "0.83.0",
395
395
  "smol-toml": "1.9.1",
396
396
  "typescript5": "npm:typescript@5.9.3"
397
397
  },
398
398
  "peerDependencies": {
399
399
  "@modelcontextprotocol/sdk": "^1.31.0",
400
- "agent-blackboard": "^0.6.1",
401
400
  "no-mistakes": "*",
402
401
  "typescript": ">=5",
403
402
  "vitest": ">=5"
404
403
  },
405
404
  "peerDependenciesMeta": {
406
- "agent-blackboard": {
407
- "optional": true
408
- },
409
405
  "@modelcontextprotocol/sdk": {
410
406
  "optional": true
411
407
  },