@tryinget/pi-agent-vent 0.2.0 → 0.2.1
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/docs/project/2026-06-05-self-toolbox-agent-vent-diagnostic-boundary.md +3 -0
- package/extensions/agent-vent.ts +7 -0
- package/package.json +4 -4
- package/scripts/docs-list.sh +5 -0
- package/scripts/quality-gate.sh +5 -0
- package/scripts/release-artifact-check.mjs +35 -5
- package/scripts/release-check.sh +48 -12
- package/scripts/release-smoke-check.mjs +6 -0
- package/scripts/release-smoke.sh +5 -0
- package/scripts/validate-structure.mjs +5 -0
- package/scripts/validate-structure.sh +5 -0
- package/src/vent-store.js +7 -0
- package/tests/agent-vent-extension.test.js +24 -0
- package/tests/release-artifact-check.test.js +69 -2
- package/tests/release-smoke-check.test.js +11 -5
- package/tests/vent-store.test.js +20 -1
|
@@ -48,6 +48,9 @@ Rules:
|
|
|
48
48
|
- `toolbox` activation only makes the `agent_vent` tool callable; it does not create or preview a diagnostic record.
|
|
49
49
|
- `agent_vent action=preview` sanitizes the would-be record and runs the anti-junk quality check without writing the store.
|
|
50
50
|
- `agent_vent action=record` writes only if the local quality check accepts the payload.
|
|
51
|
+
- `self.diagnostic_candidate.v1` exposes separate `agentVentSuggestionAllowed` and `agentVentRecordAllowed` permissions. Plain `no agent_vent` / `no-agent_vent` constraints suppress activation, preview, and record guidance. Record-only `no agent_vent record` / `no-agent_vent-record` constraints preserve non-mutating preview guidance while marking durable recording forbidden.
|
|
52
|
+
- ASC category `context_alignment` is accepted by `agent_vent` as a compatibility alias and normalized to canonical category `other`; the alias does not add a new canonical category.
|
|
53
|
+
- The toolbox bundle currently exposes only its `default` diagnostic profile. Keep a call read-only by choosing `agent_vent` actions such as `preview`, `review`, or `stats`; do not invent `profile=read`.
|
|
51
54
|
- Generic low-signal summaries such as `done` are rejected for `record`; use `preview` to inspect issues and warnings before writing.
|
|
52
55
|
- Human/operator judgment still decides whether any recurrence should become an AK task, GitHub issue, incident review, evidence record, publication, or owner-surface handoff.
|
|
53
56
|
|
package/extensions/agent-vent.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
// ---
|
|
2
|
+
// summary: registers the agent_vent tool and command for local diagnostic review workflows
|
|
3
|
+
// read_when:
|
|
4
|
+
// - changing agent vent actions, command parsing, tool schemas, or authority boundaries
|
|
5
|
+
// ---
|
|
6
|
+
|
|
1
7
|
import path from "node:path";
|
|
2
8
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
9
|
import { Type } from "typebox";
|
|
@@ -92,6 +98,7 @@ const CATEGORY_ALIAS_INPUTS = [
|
|
|
92
98
|
"docs_gap",
|
|
93
99
|
"context_window",
|
|
94
100
|
"context_friction",
|
|
101
|
+
"context_alignment",
|
|
95
102
|
"tooling_friction",
|
|
96
103
|
] as const;
|
|
97
104
|
const CATEGORY_INPUTS = [...CATEGORIES, ...CATEGORY_ALIAS_INPUTS] as const;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tryinget/pi-agent-vent",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Local pi tool for agents to record recurring frustrations, bugs, and workflow friction without creating incidents or external telemetry",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
@@ -87,8 +87,8 @@
|
|
|
87
87
|
},
|
|
88
88
|
"devDependencies": {
|
|
89
89
|
"@biomejs/biome": "2.3.14",
|
|
90
|
-
"@earendil-works/pi-ai": "0.
|
|
91
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
90
|
+
"@earendil-works/pi-ai": "0.83.0",
|
|
91
|
+
"@earendil-works/pi-coding-agent": "0.83.0"
|
|
92
92
|
},
|
|
93
93
|
"overrides": {
|
|
94
94
|
"fast-xml-parser": "5.3.6"
|
|
@@ -99,6 +99,6 @@
|
|
|
99
99
|
"typebox": "*"
|
|
100
100
|
},
|
|
101
101
|
"dependencies": {
|
|
102
|
-
"typebox": "
|
|
102
|
+
"typebox": "1.3.7"
|
|
103
103
|
}
|
|
104
104
|
}
|
package/scripts/docs-list.sh
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
+
# ---
|
|
3
|
+
# summary: locates and invokes documentation listing support for pi-agent-vent
|
|
4
|
+
# read_when:
|
|
5
|
+
# - enumerating package docs or overriding the docs-list implementation
|
|
6
|
+
# ---
|
|
2
7
|
set -euo pipefail
|
|
3
8
|
|
|
4
9
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
package/scripts/quality-gate.sh
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
+
# ---
|
|
3
|
+
# summary: runs pi-agent-vent quality stages through the root gate or package fallback
|
|
4
|
+
# read_when:
|
|
5
|
+
# - checking lint, types, tests, or scaffold structure in varied checkout layouts
|
|
6
|
+
# ---
|
|
2
7
|
set -euo pipefail
|
|
3
8
|
|
|
4
9
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
// ---
|
|
2
|
+
// summary: validates packed file whitelists and local links in published markdown
|
|
3
|
+
// read_when:
|
|
4
|
+
// - changing pi-agent-vent package contents or release artifact policy
|
|
5
|
+
// ---
|
|
6
|
+
|
|
1
7
|
import fs from "node:fs";
|
|
2
8
|
import path from "node:path";
|
|
3
9
|
|
|
@@ -10,7 +16,7 @@ export const assertSafePackPath = (filePath) => {
|
|
|
10
16
|
return filePath;
|
|
11
17
|
};
|
|
12
18
|
|
|
13
|
-
export const parsePackJson = (packJsonText) => {
|
|
19
|
+
export const parsePackJson = (packJsonText, expectedIdentity = null) => {
|
|
14
20
|
let pack;
|
|
15
21
|
try {
|
|
16
22
|
pack = JSON.parse(packJsonText || "[]");
|
|
@@ -18,11 +24,30 @@ export const parsePackJson = (packJsonText) => {
|
|
|
18
24
|
throw new Error(`Could not parse npm pack --dry-run --json output: ${error.message}`);
|
|
19
25
|
}
|
|
20
26
|
|
|
21
|
-
|
|
27
|
+
let packEntry = null;
|
|
28
|
+
if (Array.isArray(pack) && pack.length === 1) {
|
|
29
|
+
[packEntry] = pack;
|
|
30
|
+
} else if (pack && typeof pack === "object") {
|
|
31
|
+
const entries = Object.entries(pack);
|
|
32
|
+
if (entries.length === 1 && entries[0][0] === entries[0][1]?.name) {
|
|
33
|
+
packEntry = entries[0][1];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (!packEntry || !Array.isArray(packEntry.files)) {
|
|
22
37
|
throw new Error("Could not parse npm pack --dry-run --json output.");
|
|
23
38
|
}
|
|
39
|
+
if (
|
|
40
|
+
expectedIdentity &&
|
|
41
|
+
(packEntry.name !== expectedIdentity.name ||
|
|
42
|
+
packEntry.version !== expectedIdentity.version ||
|
|
43
|
+
packEntry.id !== `${expectedIdentity.name}@${expectedIdentity.version}`)
|
|
44
|
+
) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`npm pack identity mismatch: expected ${expectedIdentity.name}@${expectedIdentity.version}.`,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
24
49
|
|
|
25
|
-
return
|
|
50
|
+
return packEntry.files
|
|
26
51
|
.map((file) => normalizePackPath(String(file.path || "")))
|
|
27
52
|
.filter(Boolean)
|
|
28
53
|
.map(assertSafePackPath)
|
|
@@ -182,8 +207,13 @@ export const runReleaseArtifactCheck = ({
|
|
|
182
207
|
cwd = process.cwd(),
|
|
183
208
|
log = console.log,
|
|
184
209
|
}) => {
|
|
185
|
-
const
|
|
186
|
-
const
|
|
210
|
+
const manifestPath = path.join(cwd, "package.json");
|
|
211
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
212
|
+
const actualFiles = parsePackJson(packJsonText, {
|
|
213
|
+
name: manifest.name,
|
|
214
|
+
version: manifest.version,
|
|
215
|
+
});
|
|
216
|
+
const filesEntries = readPackageFilesContract(manifestPath);
|
|
187
217
|
const whitelist = validatePackageFilesWhitelist({ actualFiles, filesEntries, cwd });
|
|
188
218
|
|
|
189
219
|
if (whitelist.missing.length || whitelist.extra.length) {
|
package/scripts/release-check.sh
CHANGED
|
@@ -1,9 +1,42 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
+
# ---
|
|
3
|
+
# summary: exercises pi-agent-vent packaging, isolated installation, and release smoke gates
|
|
4
|
+
# read_when:
|
|
5
|
+
# - preparing a release or investigating tarball and pi installation failures
|
|
6
|
+
# ---
|
|
2
7
|
set -euo pipefail
|
|
3
8
|
|
|
4
9
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
10
|
+
REPO_ROOT="$(cd "$ROOT_DIR/../.." && pwd)"
|
|
5
11
|
cd "$ROOT_DIR"
|
|
6
12
|
|
|
13
|
+
# Scratch-root resolution, from first principles:
|
|
14
|
+
# the invariant is "release-check scratch lands on a managed, disposable root",
|
|
15
|
+
# NOT "the TMPDIR variable happens to be exported". A GitHub-hosted runner is
|
|
16
|
+
# single-tenant and ephemeral; its RUNNER_TEMP is exactly a managed scratch
|
|
17
|
+
# root for that job, so CI satisfies the invariant even though runners never
|
|
18
|
+
# export TMPDIR. Locally the strict rule is unchanged: no TMPDIR, no run.
|
|
19
|
+
if [[ -n "${TMPDIR:-}" ]]; then
|
|
20
|
+
TMP_ROOT="$TMPDIR"
|
|
21
|
+
elif [[ "${GITHUB_ACTIONS:-}" == "true" && -n "${RUNNER_TEMP:-}" && -d "${RUNNER_TEMP:-}" ]]; then
|
|
22
|
+
TMP_ROOT="$RUNNER_TEMP"
|
|
23
|
+
else
|
|
24
|
+
echo "TMPDIR must name the managed scratch root for release checks" >&2
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
mkdir -p "$TMP_ROOT"
|
|
28
|
+
TMP_ROOT="$(cd "$TMP_ROOT" && pwd -P)"
|
|
29
|
+
case "$TMP_ROOT" in
|
|
30
|
+
/|"$ROOT_DIR"|"$ROOT_DIR"/*)
|
|
31
|
+
echo "Release check refused unsafe TMPDIR: $TMP_ROOT" >&2
|
|
32
|
+
exit 1
|
|
33
|
+
;;
|
|
34
|
+
esac
|
|
35
|
+
release_tmp_dir() {
|
|
36
|
+
local label="$1"
|
|
37
|
+
mktemp -d "$TMP_ROOT/pi-agent-vent-${label}-XXXXXX"
|
|
38
|
+
}
|
|
39
|
+
|
|
7
40
|
NAME="$(node -p "JSON.parse(require('node:fs').readFileSync('package.json', 'utf8')).name")"
|
|
8
41
|
VERSION="$(node -p "JSON.parse(require('node:fs').readFileSync('package.json', 'utf8')).version")"
|
|
9
42
|
REPOSITORY_URL="$(node -p "(() => { const pkg = JSON.parse(require('node:fs').readFileSync('package.json', 'utf8')); const repo = pkg.repository; if (typeof repo === 'string') return repo.trim(); if (repo && typeof repo === 'object' && typeof repo.url === 'string') return repo.url.trim(); return ''; })()")"
|
|
@@ -18,7 +51,7 @@ with_release_npm_policy() {
|
|
|
18
51
|
local prefix="$2"
|
|
19
52
|
shift 2
|
|
20
53
|
case "$cache" in
|
|
21
|
-
/
|
|
54
|
+
"$TMP_ROOT"/pi-agent-vent-*-npm-cache-*) ;;
|
|
22
55
|
*)
|
|
23
56
|
echo "Release command refused non-isolated npm cache: $cache" >&2
|
|
24
57
|
return 1
|
|
@@ -26,7 +59,7 @@ with_release_npm_policy() {
|
|
|
26
59
|
esac
|
|
27
60
|
if [[ "$prefix" != "-" ]]; then
|
|
28
61
|
case "$prefix" in
|
|
29
|
-
/
|
|
62
|
+
"$TMP_ROOT"/pi-agent-vent-*-npm-prefix-*) ;;
|
|
30
63
|
*)
|
|
31
64
|
echo "Release command refused non-isolated npm prefix: $prefix" >&2
|
|
32
65
|
return 1
|
|
@@ -50,10 +83,10 @@ release_npm_install() {
|
|
|
50
83
|
local prefix="$2"
|
|
51
84
|
shift 2
|
|
52
85
|
with_release_npm_policy "$cache" "$prefix" npm install \
|
|
53
|
-
--ignore-scripts --no-audit --fund=false "$@"
|
|
86
|
+
--include=optional --ignore-scripts --no-audit --fund=false "$@"
|
|
54
87
|
}
|
|
55
88
|
|
|
56
|
-
CONTROL_NPM_CACHE="$(
|
|
89
|
+
CONTROL_NPM_CACHE="$(release_tmp_dir control-npm-cache)"
|
|
57
90
|
TEST_AGENT_DIR=""
|
|
58
91
|
TEST_NPM_PREFIX=""
|
|
59
92
|
TEST_NPM_CACHE=""
|
|
@@ -97,6 +130,9 @@ fi
|
|
|
97
130
|
echo "== npm pack --dry-run --json"
|
|
98
131
|
PACK_JSON="$(npm --cache "$CONTROL_NPM_CACHE" pack --dry-run --json)"
|
|
99
132
|
echo "$PACK_JSON"
|
|
133
|
+
if [[ -f "$REPO_ROOT/scripts/npm-pack-json.mjs" ]]; then
|
|
134
|
+
PACK_JSON="$(printf '%s' "$PACK_JSON" | node "$REPO_ROOT/scripts/npm-pack-json.mjs")"
|
|
135
|
+
fi
|
|
100
136
|
|
|
101
137
|
PACK_JSON="$PACK_JSON" node ./scripts/release-artifact-check.mjs
|
|
102
138
|
|
|
@@ -120,8 +156,8 @@ TARBALL="$(npm --cache "$CONTROL_NPM_CACHE" pack --silent | tail -n 1)"
|
|
|
120
156
|
TARBALL_PATH="$ROOT_DIR/$TARBALL"
|
|
121
157
|
echo "Tarball: $TARBALL_PATH"
|
|
122
158
|
|
|
123
|
-
TARBALL_CHECK_DIR="$(
|
|
124
|
-
TARBALL_NPM_CACHE="$(
|
|
159
|
+
TARBALL_CHECK_DIR="$(release_tmp_dir tarball-check)"
|
|
160
|
+
TARBALL_NPM_CACHE="$(release_tmp_dir tarball-npm-cache)"
|
|
125
161
|
echo "== unpacked tarball package contract"
|
|
126
162
|
tar -xzf "$TARBALL_PATH" -C "$TARBALL_CHECK_DIR"
|
|
127
163
|
(
|
|
@@ -132,9 +168,9 @@ tar -xzf "$TARBALL_PATH" -C "$TARBALL_CHECK_DIR"
|
|
|
132
168
|
npm run check
|
|
133
169
|
)
|
|
134
170
|
|
|
135
|
-
ARTIFACT_NPM_PREFIX="$(
|
|
136
|
-
ARTIFACT_NPM_CACHE="$(
|
|
137
|
-
ARTIFACT_TOOL_VENT_DIR="$(
|
|
171
|
+
ARTIFACT_NPM_PREFIX="$(release_tmp_dir artifact-npm-prefix)"
|
|
172
|
+
ARTIFACT_NPM_CACHE="$(release_tmp_dir artifact-npm-cache)"
|
|
173
|
+
ARTIFACT_TOOL_VENT_DIR="$(release_tmp_dir artifact-tool-store)"
|
|
138
174
|
ARTIFACT_PACKAGE_ROOT="$(npm --prefix "$ARTIFACT_NPM_PREFIX" root -g)/$NAME"
|
|
139
175
|
|
|
140
176
|
case "$ARTIFACT_PACKAGE_ROOT" in
|
|
@@ -173,9 +209,9 @@ else
|
|
|
173
209
|
exit 1
|
|
174
210
|
fi
|
|
175
211
|
|
|
176
|
-
TEST_AGENT_DIR="$(
|
|
177
|
-
TEST_NPM_PREFIX="$(
|
|
178
|
-
TEST_NPM_CACHE="$(
|
|
212
|
+
TEST_AGENT_DIR="$(release_tmp_dir pi-agent-dir)"
|
|
213
|
+
TEST_NPM_PREFIX="$(release_tmp_dir pi-npm-prefix)"
|
|
214
|
+
TEST_NPM_CACHE="$(release_tmp_dir pi-npm-cache)"
|
|
179
215
|
|
|
180
216
|
cp "$HOME/.pi/agent/auth.json" "$TEST_AGENT_DIR/auth.json"
|
|
181
217
|
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
// ---
|
|
2
|
+
// summary: supplies assertions and isolated tool-path probes for release smoke testing
|
|
3
|
+
// read_when:
|
|
4
|
+
// - changing installed-artifact validation or agent_vent release smoke behavior
|
|
5
|
+
// ---
|
|
6
|
+
|
|
1
7
|
import fs from "node:fs";
|
|
2
8
|
import os from "node:os";
|
|
3
9
|
import path from "node:path";
|
package/scripts/release-smoke.sh
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
+
# ---
|
|
3
|
+
# summary: smoke-tests the installed agent_vent package through isolated pi discovery
|
|
4
|
+
# read_when:
|
|
5
|
+
# - verifying tarball installation, command output, or registered-tool execution
|
|
6
|
+
# ---
|
|
2
7
|
set -euo pipefail
|
|
3
8
|
|
|
4
9
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// ---
|
|
3
|
+
// summary: enforces pi-agent-vent manifest, script, extension, and lane structure contracts
|
|
4
|
+
// read_when:
|
|
5
|
+
// - editing package metadata, copier answers, publish files, or required npm scripts
|
|
6
|
+
// ---
|
|
2
7
|
|
|
3
8
|
import fs from "node:fs";
|
|
4
9
|
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
+
# ---
|
|
3
|
+
# summary: verifies the pi-agent-vent scaffold tree, executables, and markdown frontmatter
|
|
4
|
+
# read_when:
|
|
5
|
+
# - checking filesystem conformance after package scaffold or documentation changes
|
|
6
|
+
# ---
|
|
2
7
|
set -euo pipefail
|
|
3
8
|
|
|
4
9
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
package/src/vent-store.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
// ---
|
|
2
|
+
// summary: implements private agent vent storage, review projections, and retention safeguards
|
|
3
|
+
// read_when:
|
|
4
|
+
// - changing diagnostic record schemas, redaction, curation, review, export, or retention logic
|
|
5
|
+
// ---
|
|
6
|
+
|
|
1
7
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
8
|
import fs from "node:fs";
|
|
3
9
|
import os from "node:os";
|
|
@@ -32,6 +38,7 @@ export const CATEGORY_ALIASES = Object.freeze({
|
|
|
32
38
|
docs_gap: "documentation",
|
|
33
39
|
context_window: "context_loss",
|
|
34
40
|
context_friction: "context_loss",
|
|
41
|
+
context_alignment: "other",
|
|
35
42
|
tooling_friction: "friction",
|
|
36
43
|
});
|
|
37
44
|
export const SEVERITIES = ["low", "medium", "high", "critical"];
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
// ---
|
|
2
|
+
// summary: tests agent_vent tool registration, commands, actions, and authority-safe output
|
|
3
|
+
// read_when:
|
|
4
|
+
// - modifying the extension schema, command surface, lifecycle actions, or smoke contracts
|
|
5
|
+
// ---
|
|
6
|
+
|
|
1
7
|
import assert from "node:assert/strict";
|
|
2
8
|
import fs from "node:fs";
|
|
3
9
|
import os from "node:os";
|
|
@@ -46,6 +52,7 @@ test("agent_vent tool schema stays aligned with retention candidate and compare
|
|
|
46
52
|
assert.match(schemaText, /"compare"/);
|
|
47
53
|
assert.match(schemaText, /"preview"/);
|
|
48
54
|
assert.match(schemaText, /"workflow_friction"/);
|
|
55
|
+
assert.match(schemaText, /"context_alignment"/);
|
|
49
56
|
assert.match(schemaText, /"candidates"/);
|
|
50
57
|
assert.match(schemaText, /"history"/);
|
|
51
58
|
assert.match(schemaText, /retentionCandidateState/);
|
|
@@ -549,6 +556,23 @@ test("agent_vent accepts category aliases for tool records and command filters",
|
|
|
549
556
|
"workflow:workflow-friction-alias-should-normalize",
|
|
550
557
|
);
|
|
551
558
|
|
|
559
|
+
const alignmentPreview = await tool.execute(
|
|
560
|
+
"tool-call-context-alignment-alias",
|
|
561
|
+
{
|
|
562
|
+
action: "preview",
|
|
563
|
+
summary: "Context alignment candidate should remain callable",
|
|
564
|
+
category: "context_alignment",
|
|
565
|
+
},
|
|
566
|
+
undefined,
|
|
567
|
+
undefined,
|
|
568
|
+
{
|
|
569
|
+
cwd: "/repo",
|
|
570
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
571
|
+
},
|
|
572
|
+
);
|
|
573
|
+
assert.equal(alignmentPreview.details.wouldRecord, true);
|
|
574
|
+
assert.equal(alignmentPreview.details.recordPreview.category, "other");
|
|
575
|
+
|
|
552
576
|
const reviewResult = await tool.execute(
|
|
553
577
|
"tool-call-category-alias-review",
|
|
554
578
|
{ action: "review", category: "workflow_friction" },
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
// ---
|
|
2
|
+
// summary: tests package whitelist parsing and published markdown link validation
|
|
3
|
+
// read_when:
|
|
4
|
+
// - changing release artifact checks, pack output handling, or files-array policy
|
|
5
|
+
// ---
|
|
6
|
+
|
|
1
7
|
import assert from "node:assert/strict";
|
|
2
8
|
import fs from "node:fs";
|
|
3
9
|
import os from "node:os";
|
|
@@ -77,6 +83,28 @@ test("pack json parser fails closed for malformed, stale, or unsafe pack output"
|
|
|
77
83
|
);
|
|
78
84
|
});
|
|
79
85
|
|
|
86
|
+
test("pack json parser accepts canonical arrays and npm 12 package-keyed objects", () => {
|
|
87
|
+
const entry = { name: "@tryinget/pi-agent-vent", files: [{ path: "package.json" }] };
|
|
88
|
+
assert.deepEqual(parsePackJson(JSON.stringify([entry])), ["package.json"]);
|
|
89
|
+
assert.deepEqual(parsePackJson(JSON.stringify({ "@tryinget/pi-agent-vent": entry })), [
|
|
90
|
+
"package.json",
|
|
91
|
+
]);
|
|
92
|
+
assert.throws(
|
|
93
|
+
() =>
|
|
94
|
+
parsePackJson(
|
|
95
|
+
JSON.stringify({
|
|
96
|
+
"@tryinget/pi-agent-vent": entry,
|
|
97
|
+
"@tryinget/other": entry,
|
|
98
|
+
}),
|
|
99
|
+
),
|
|
100
|
+
/Could not parse npm pack/,
|
|
101
|
+
);
|
|
102
|
+
assert.throws(
|
|
103
|
+
() => parsePackJson(JSON.stringify({ "@tryinget/wrong": entry })),
|
|
104
|
+
/Could not parse npm pack/,
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
80
108
|
test("package files whitelist fails closed for wildcard entries", () => {
|
|
81
109
|
assert.throws(
|
|
82
110
|
() =>
|
|
@@ -114,13 +142,19 @@ test("package files whitelist detects missing and extra artifact entries", () =>
|
|
|
114
142
|
test("release artifact check accepts directory files entries and always-included metadata", () => {
|
|
115
143
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-vent-release-check-ok-"));
|
|
116
144
|
try {
|
|
117
|
-
fs.writeFileSync(
|
|
145
|
+
fs.writeFileSync(
|
|
146
|
+
path.join(dir, "package.json"),
|
|
147
|
+
JSON.stringify({ name: "@tryinget/pi-agent-vent", version: "0.2.0", files: ["docs"] }),
|
|
148
|
+
);
|
|
118
149
|
fs.writeFileSync(path.join(dir, "README.md"), "[guide](docs/guide.md)\n");
|
|
119
150
|
fs.writeFileSync(path.join(dir, "LICENSE"), "local test license\n");
|
|
120
151
|
fs.mkdirSync(path.join(dir, "docs"));
|
|
121
152
|
fs.writeFileSync(path.join(dir, "docs", "guide.md"), "# Guide\n");
|
|
122
153
|
const packJsonText = JSON.stringify([
|
|
123
154
|
{
|
|
155
|
+
id: "@tryinget/pi-agent-vent@0.2.0",
|
|
156
|
+
name: "@tryinget/pi-agent-vent",
|
|
157
|
+
version: "0.2.0",
|
|
124
158
|
files: [
|
|
125
159
|
{ path: "README.md" },
|
|
126
160
|
{ path: "LICENSE" },
|
|
@@ -142,18 +176,51 @@ test("release artifact check accepts directory files entries and always-included
|
|
|
142
176
|
}
|
|
143
177
|
});
|
|
144
178
|
|
|
179
|
+
test("release artifact check rejects pack identity that differs from the local manifest", () => {
|
|
180
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-vent-release-identity-"));
|
|
181
|
+
try {
|
|
182
|
+
fs.writeFileSync(
|
|
183
|
+
path.join(dir, "package.json"),
|
|
184
|
+
JSON.stringify({ name: "@tryinget/pi-agent-vent", version: "0.2.0", files: ["README.md"] }),
|
|
185
|
+
);
|
|
186
|
+
fs.writeFileSync(path.join(dir, "README.md"), "# Agent Vent\n");
|
|
187
|
+
const packJsonText = JSON.stringify([
|
|
188
|
+
{
|
|
189
|
+
id: "@tryinget/not-pi-agent-vent@999.0.0",
|
|
190
|
+
name: "@tryinget/not-pi-agent-vent",
|
|
191
|
+
version: "999.0.0",
|
|
192
|
+
files: [{ path: "README.md" }, { path: "package.json" }],
|
|
193
|
+
},
|
|
194
|
+
]);
|
|
195
|
+
|
|
196
|
+
assert.throws(
|
|
197
|
+
() => runReleaseArtifactCheck({ cwd: dir, packJsonText, log: () => {} }),
|
|
198
|
+
/npm pack identity mismatch/,
|
|
199
|
+
);
|
|
200
|
+
} finally {
|
|
201
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
145
205
|
test("release artifact check reports packaged markdown failures with actionable wording", () => {
|
|
146
206
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "agent-vent-release-check-"));
|
|
147
207
|
try {
|
|
148
208
|
fs.writeFileSync(
|
|
149
209
|
path.join(dir, "package.json"),
|
|
150
|
-
JSON.stringify({
|
|
210
|
+
JSON.stringify({
|
|
211
|
+
name: "@tryinget/pi-agent-vent",
|
|
212
|
+
version: "0.2.0",
|
|
213
|
+
files: ["README.md", "docs/guide.md"],
|
|
214
|
+
}),
|
|
151
215
|
);
|
|
152
216
|
fs.mkdirSync(path.join(dir, "docs"));
|
|
153
217
|
fs.writeFileSync(path.join(dir, "README.md"), "[missing](docs/missing.md)\n");
|
|
154
218
|
fs.writeFileSync(path.join(dir, "docs", "guide.md"), "# Guide\n");
|
|
155
219
|
const packJsonText = JSON.stringify([
|
|
156
220
|
{
|
|
221
|
+
id: "@tryinget/pi-agent-vent@0.2.0",
|
|
222
|
+
name: "@tryinget/pi-agent-vent",
|
|
223
|
+
version: "0.2.0",
|
|
157
224
|
files: [{ path: "README.md" }, { path: "docs/guide.md" }, { path: "package.json" }],
|
|
158
225
|
},
|
|
159
226
|
]);
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
// ---
|
|
2
|
+
// summary: tests isolated release-smoke contracts for installed agent_vent artifacts
|
|
3
|
+
// read_when:
|
|
4
|
+
// - changing tarball source validation, package discovery, or path-tool smoke probes
|
|
5
|
+
// ---
|
|
6
|
+
|
|
1
7
|
import assert from "node:assert/strict";
|
|
2
8
|
import fs from "node:fs";
|
|
3
9
|
import os from "node:os";
|
|
@@ -17,17 +23,17 @@ import {
|
|
|
17
23
|
test("release smoke release-age bypass is gated by one exact host contract", () => {
|
|
18
24
|
const packageJson = {
|
|
19
25
|
devDependencies: {
|
|
20
|
-
"@earendil-works/pi-ai": "0.
|
|
21
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
26
|
+
"@earendil-works/pi-ai": "0.83.0",
|
|
27
|
+
"@earendil-works/pi-coding-agent": "0.83.0",
|
|
22
28
|
},
|
|
23
29
|
};
|
|
24
|
-
assert.equal(assertExactHostContract({ packageJson, hostVersion: "0.
|
|
30
|
+
assert.equal(assertExactHostContract({ packageJson, hostVersion: "0.83.0" }), "0.83.0");
|
|
25
31
|
assert.throws(
|
|
26
|
-
() => assertExactHostContract({ packageJson, hostVersion: "^0.
|
|
32
|
+
() => assertExactHostContract({ packageJson, hostVersion: "^0.83.0" }),
|
|
27
33
|
/requires an exact Pi host version/,
|
|
28
34
|
);
|
|
29
35
|
assert.throws(
|
|
30
|
-
() => assertExactHostContract({ packageJson, hostVersion: "0.
|
|
36
|
+
() => assertExactHostContract({ packageJson, hostVersion: "0.83.1" }),
|
|
31
37
|
/host contract mismatch/,
|
|
32
38
|
);
|
|
33
39
|
});
|
package/tests/vent-store.test.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
// ---
|
|
2
|
+
// summary: tests agent vent persistence, redaction, review projections, and retention safety
|
|
3
|
+
// read_when:
|
|
4
|
+
// - modifying vent-store records, filters, curation, exports, backups, or filesystem guards
|
|
5
|
+
// ---
|
|
6
|
+
|
|
1
7
|
import assert from "node:assert/strict";
|
|
2
8
|
import { createHash } from "node:crypto";
|
|
3
9
|
import fs from "node:fs";
|
|
@@ -121,18 +127,31 @@ test("category aliases normalize at record and review-filter boundaries", () =>
|
|
|
121
127
|
summary: "Missing affordance alias should preserve meaning",
|
|
122
128
|
category: "missing_affordance",
|
|
123
129
|
});
|
|
130
|
+
const alignment = createVentRecord({
|
|
131
|
+
summary: "Context alignment candidate should remain callable",
|
|
132
|
+
category: "context_alignment",
|
|
133
|
+
});
|
|
124
134
|
|
|
125
135
|
assert.equal(workflow.category, "workflow");
|
|
126
136
|
assert.equal(workflow.recurrenceKey, "workflow:workflow-alias");
|
|
127
137
|
assert.equal(missing.category, "missing_capability");
|
|
128
138
|
assert.match(missing.recurrenceKey, /^missing_capability:/);
|
|
139
|
+
assert.equal(alignment.category, "other");
|
|
140
|
+
assert.match(alignment.recurrenceKey, /^other:/);
|
|
129
141
|
|
|
130
|
-
const queue = summarizeReviewQueue([workflow, missing], [], {
|
|
142
|
+
const queue = summarizeReviewQueue([workflow, missing, alignment], [], {
|
|
131
143
|
filters: { category: "workflow_friction" },
|
|
132
144
|
});
|
|
133
145
|
assert.equal(queue.filters.category, "workflow");
|
|
134
146
|
assert.equal(queue.matchingGroupCount, 1);
|
|
135
147
|
assert.equal(queue.items[0].recurrenceKey, workflow.recurrenceKey);
|
|
148
|
+
|
|
149
|
+
const alignmentQueue = summarizeReviewQueue([workflow, missing, alignment], [], {
|
|
150
|
+
filters: { category: "context_alignment" },
|
|
151
|
+
});
|
|
152
|
+
assert.equal(alignmentQueue.filters.category, "other");
|
|
153
|
+
assert.equal(alignmentQueue.matchingGroupCount, 1);
|
|
154
|
+
assert.equal(alignmentQueue.items[0].recurrenceKey, alignment.recurrenceKey);
|
|
136
155
|
});
|
|
137
156
|
|
|
138
157
|
test("explicit recurrence keys are redacted before slugging", () => {
|