@tryinget/pi-agent-vent 0.1.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/LICENSE +78 -0
- package/README.md +188 -0
- package/biome.jsonc +43 -0
- package/docs/adr/2026-05-22-agent-vent-retention-delete-policy.md +65 -0
- package/docs/engineering.local.md +57 -0
- package/docs/project/2026-05-21-agent-vent-design.md +182 -0
- package/docs/project/2026-05-21-agent-vent-implementation-plan.md +66 -0
- package/docs/project/extension-sop.md +52 -0
- package/docs/project/product-posture.md +191 -0
- package/docs/project/trusted-publishing.md +53 -0
- package/docs/project/vision.md +70 -0
- package/examples/.gitkeep +0 -0
- package/extensions/agent-vent.ts +1384 -0
- package/package.json +95 -0
- package/policy/engineering-lane.json +22 -0
- package/policy/security-policy.json +10 -0
- package/prompts/implementation-planning.md +20 -0
- package/prompts/security-review.md +20 -0
- package/scripts/docs-list.sh +50 -0
- package/scripts/quality-gate.sh +168 -0
- package/scripts/release-artifact-check.mjs +221 -0
- package/scripts/release-check.sh +196 -0
- package/scripts/release-smoke-check.mjs +367 -0
- package/scripts/release-smoke.sh +90 -0
- package/scripts/validate-structure.mjs +248 -0
- package/scripts/validate-structure.sh +145 -0
- package/src/vent-store.js +2839 -0
- package/tests/.gitkeep +0 -0
- package/tests/agent-vent-extension.test.js +762 -0
- package/tests/release-artifact-check.test.js +168 -0
- package/tests/release-smoke-check.test.js +235 -0
- package/tests/vent-store.test.js +1972 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
cd "$ROOT_DIR"
|
|
6
|
+
|
|
7
|
+
NAME="$(node -p "JSON.parse(require('node:fs').readFileSync('package.json', 'utf8')).name")"
|
|
8
|
+
VERSION="$(node -p "JSON.parse(require('node:fs').readFileSync('package.json', 'utf8')).version")"
|
|
9
|
+
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 ''; })()")"
|
|
10
|
+
|
|
11
|
+
echo "== release-check: ${NAME}@${VERSION}"
|
|
12
|
+
|
|
13
|
+
if [[ -z "$REPOSITORY_URL" ]]; then
|
|
14
|
+
echo "package.json repository.url is required for provenance release publishing." >&2
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
if [[ "$NAME" != "${NAME,,}" ]]; then
|
|
19
|
+
echo "Invalid npm package name: must be lowercase: $NAME" >&2
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
echo "== npm pack --dry-run --json"
|
|
24
|
+
PACK_JSON="$(npm pack --dry-run --json)"
|
|
25
|
+
echo "$PACK_JSON"
|
|
26
|
+
|
|
27
|
+
PACK_JSON="$PACK_JSON" node ./scripts/release-artifact-check.mjs
|
|
28
|
+
|
|
29
|
+
echo "== npm publish --dry-run"
|
|
30
|
+
set +e
|
|
31
|
+
PUBLISH_DRY_RUN_OUTPUT="$(npm publish --dry-run 2>&1)"
|
|
32
|
+
PUBLISH_DRY_RUN_EXIT=$?
|
|
33
|
+
set -e
|
|
34
|
+
echo "$PUBLISH_DRY_RUN_OUTPUT"
|
|
35
|
+
if [[ "$PUBLISH_DRY_RUN_EXIT" -ne 0 ]]; then
|
|
36
|
+
if grep -qiE "You cannot publish over the previously published versions|previously published version .* is higher than the new version" <<<"$PUBLISH_DRY_RUN_OUTPUT"; then
|
|
37
|
+
echo "npm publish --dry-run hit registry version guard (${VERSION}); continuing."
|
|
38
|
+
else
|
|
39
|
+
echo "npm publish --dry-run failed." >&2
|
|
40
|
+
exit "$PUBLISH_DRY_RUN_EXIT"
|
|
41
|
+
fi
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
TEST_AGENT_DIR=""
|
|
45
|
+
TEST_NPM_PREFIX=""
|
|
46
|
+
TEST_NPM_CACHE=""
|
|
47
|
+
ARTIFACT_NPM_PREFIX=""
|
|
48
|
+
ARTIFACT_NPM_CACHE=""
|
|
49
|
+
ARTIFACT_TOOL_VENT_DIR=""
|
|
50
|
+
TARBALL_CHECK_DIR=""
|
|
51
|
+
TARBALL_PATH=""
|
|
52
|
+
cleanup() {
|
|
53
|
+
if [[ "${KEEP_RELEASE_ARTIFACTS:-0}" != "1" ]]; then
|
|
54
|
+
if [[ -n "$TEST_AGENT_DIR" && -d "$TEST_AGENT_DIR" ]]; then
|
|
55
|
+
rm -rf "$TEST_AGENT_DIR"
|
|
56
|
+
fi
|
|
57
|
+
if [[ -n "$TEST_NPM_PREFIX" && -d "$TEST_NPM_PREFIX" ]]; then
|
|
58
|
+
rm -rf "$TEST_NPM_PREFIX"
|
|
59
|
+
fi
|
|
60
|
+
if [[ -n "$TEST_NPM_CACHE" && -d "$TEST_NPM_CACHE" ]]; then
|
|
61
|
+
rm -rf "$TEST_NPM_CACHE"
|
|
62
|
+
fi
|
|
63
|
+
if [[ -n "$ARTIFACT_NPM_PREFIX" && -d "$ARTIFACT_NPM_PREFIX" ]]; then
|
|
64
|
+
rm -rf "$ARTIFACT_NPM_PREFIX"
|
|
65
|
+
fi
|
|
66
|
+
if [[ -n "$ARTIFACT_NPM_CACHE" && -d "$ARTIFACT_NPM_CACHE" ]]; then
|
|
67
|
+
rm -rf "$ARTIFACT_NPM_CACHE"
|
|
68
|
+
fi
|
|
69
|
+
if [[ -n "$ARTIFACT_TOOL_VENT_DIR" && -d "$ARTIFACT_TOOL_VENT_DIR" ]]; then
|
|
70
|
+
rm -rf "$ARTIFACT_TOOL_VENT_DIR"
|
|
71
|
+
fi
|
|
72
|
+
if [[ -n "$TARBALL_CHECK_DIR" && -d "$TARBALL_CHECK_DIR" ]]; then
|
|
73
|
+
rm -rf "$TARBALL_CHECK_DIR"
|
|
74
|
+
fi
|
|
75
|
+
if [[ -n "$TARBALL_PATH" && -f "$TARBALL_PATH" ]]; then
|
|
76
|
+
rm -f "$TARBALL_PATH"
|
|
77
|
+
fi
|
|
78
|
+
fi
|
|
79
|
+
}
|
|
80
|
+
trap cleanup EXIT
|
|
81
|
+
|
|
82
|
+
echo "== npm pack"
|
|
83
|
+
TARBALL="$(npm pack --silent | tail -n 1)"
|
|
84
|
+
TARBALL_PATH="$ROOT_DIR/$TARBALL"
|
|
85
|
+
echo "Tarball: $TARBALL_PATH"
|
|
86
|
+
|
|
87
|
+
TARBALL_CHECK_DIR="$(mktemp -d /tmp/pi-agent-vent-tarball-check-XXXXXX)"
|
|
88
|
+
echo "== unpacked tarball package contract"
|
|
89
|
+
tar -xzf "$TARBALL_PATH" -C "$TARBALL_CHECK_DIR"
|
|
90
|
+
(
|
|
91
|
+
cd "$TARBALL_CHECK_DIR/package"
|
|
92
|
+
npm install --ignore-scripts --no-audit --fund=false
|
|
93
|
+
npm run check
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
ARTIFACT_NPM_PREFIX="$(mktemp -d /tmp/pi-agent-vent-artifact-npm-prefix-XXXXXX)"
|
|
97
|
+
ARTIFACT_NPM_CACHE="$(mktemp -d /tmp/pi-agent-vent-artifact-npm-cache-XXXXXX)"
|
|
98
|
+
ARTIFACT_TOOL_VENT_DIR="$(mktemp -d /tmp/pi-agent-vent-artifact-tool-store-XXXXXX)"
|
|
99
|
+
ARTIFACT_PACKAGE_ROOT="$(npm --prefix "$ARTIFACT_NPM_PREFIX" root -g)/$NAME"
|
|
100
|
+
|
|
101
|
+
case "$ARTIFACT_PACKAGE_ROOT" in
|
|
102
|
+
"$ARTIFACT_NPM_PREFIX"/*) ;;
|
|
103
|
+
*)
|
|
104
|
+
echo "Artifact package root escaped isolated npm prefix: $ARTIFACT_PACKAGE_ROOT" >&2
|
|
105
|
+
exit 1
|
|
106
|
+
;;
|
|
107
|
+
esac
|
|
108
|
+
|
|
109
|
+
echo "== npm installed artifact shadow registered-tool smoke (no Pi auth)"
|
|
110
|
+
npm --prefix "$ARTIFACT_NPM_PREFIX" --cache "$ARTIFACT_NPM_CACHE" \
|
|
111
|
+
install --global --ignore-scripts --no-audit --fund=false "$TARBALL_PATH"
|
|
112
|
+
node ./scripts/release-smoke-check.mjs assert-installed-artifact \
|
|
113
|
+
--package-root "$ARTIFACT_PACKAGE_ROOT" \
|
|
114
|
+
--package-name "$NAME" \
|
|
115
|
+
--package-version "$VERSION"
|
|
116
|
+
node ./scripts/release-smoke-check.mjs assert-installed-tool-path \
|
|
117
|
+
--package-root "$ARTIFACT_PACKAGE_ROOT" \
|
|
118
|
+
--vent-dir "$ARTIFACT_TOOL_VENT_DIR"
|
|
119
|
+
|
|
120
|
+
if [[ "${SKIP_PI_SMOKE:-0}" == "1" ]]; then
|
|
121
|
+
echo "Skipping pi smoke tests (SKIP_PI_SMOKE=1)."
|
|
122
|
+
else
|
|
123
|
+
if ! command -v pi >/dev/null 2>&1; then
|
|
124
|
+
echo "pi CLI not found in PATH." >&2
|
|
125
|
+
exit 1
|
|
126
|
+
fi
|
|
127
|
+
if [[ ! -f "$HOME/.pi/agent/auth.json" ]]; then
|
|
128
|
+
echo "Missing $HOME/.pi/agent/auth.json (needed for isolated pi smoke tests)." >&2
|
|
129
|
+
echo "Tip: set SKIP_PI_SMOKE=1 for artifact-only checks." >&2
|
|
130
|
+
exit 1
|
|
131
|
+
fi
|
|
132
|
+
|
|
133
|
+
TEST_AGENT_DIR="$(mktemp -d /tmp/pi-extension-release-check-XXXXXX)"
|
|
134
|
+
TEST_NPM_PREFIX="$(mktemp -d /tmp/pi-extension-release-npm-prefix-XXXXXX)"
|
|
135
|
+
TEST_NPM_CACHE="$(mktemp -d /tmp/pi-extension-release-npm-cache-XXXXXX)"
|
|
136
|
+
|
|
137
|
+
cp "$HOME/.pi/agent/auth.json" "$TEST_AGENT_DIR/auth.json"
|
|
138
|
+
|
|
139
|
+
# Allow override via environment variables for different provider configurations
|
|
140
|
+
PI_TEST_DEFAULT_PROVIDER="${PI_TEST_DEFAULT_PROVIDER:-openai}"
|
|
141
|
+
PI_TEST_DEFAULT_MODEL="${PI_TEST_DEFAULT_MODEL:-gpt-4o}"
|
|
142
|
+
PI_TEST_ENABLED_MODELS="${PI_TEST_ENABLED_MODELS:-[\"openai/gpt-4*\"]}"
|
|
143
|
+
|
|
144
|
+
cat > "$TEST_AGENT_DIR/settings.json" <<JSON
|
|
145
|
+
{
|
|
146
|
+
"defaultProvider": "${PI_TEST_DEFAULT_PROVIDER}",
|
|
147
|
+
"defaultModel": "${PI_TEST_DEFAULT_MODEL}",
|
|
148
|
+
"enabledModels": ${PI_TEST_ENABLED_MODELS},
|
|
149
|
+
"npmCommand": ["npm", "--prefix", "${TEST_NPM_PREFIX}", "--cache", "${TEST_NPM_CACHE}"],
|
|
150
|
+
"extensions": []
|
|
151
|
+
}
|
|
152
|
+
JSON
|
|
153
|
+
|
|
154
|
+
echo "== pi install tarball (isolated PI_CODING_AGENT_DIR and npm prefix)"
|
|
155
|
+
PACKAGE_SPEC="npm:$TARBALL_PATH"
|
|
156
|
+
NPM_CONFIG_PREFIX="$TEST_NPM_PREFIX" NPM_CONFIG_CACHE="$TEST_NPM_CACHE" \
|
|
157
|
+
PI_CODING_AGENT_DIR="$TEST_AGENT_DIR" pi install "$PACKAGE_SPEC"
|
|
158
|
+
|
|
159
|
+
echo "== verify tarball package recorded in settings"
|
|
160
|
+
TEST_AGENT_DIR="$TEST_AGENT_DIR" PACKAGE_SPEC="$PACKAGE_SPEC" node <<'NODE'
|
|
161
|
+
const fs = require("node:fs");
|
|
162
|
+
const path = require("node:path");
|
|
163
|
+
const settingsPath = path.join(process.env.TEST_AGENT_DIR, "settings.json");
|
|
164
|
+
const packageSpec = process.env.PACKAGE_SPEC;
|
|
165
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
166
|
+
const packages = Array.isArray(settings.packages) ? settings.packages : [];
|
|
167
|
+
const found = packages.some((entry) => {
|
|
168
|
+
if (typeof entry === "string") return entry === packageSpec;
|
|
169
|
+
if (entry && typeof entry === "object") return entry.source === packageSpec;
|
|
170
|
+
return false;
|
|
171
|
+
});
|
|
172
|
+
if (!found) {
|
|
173
|
+
console.error(`Could not find ${packageSpec} in settings.packages`);
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
console.log("Tarball package entry present in settings.packages.");
|
|
177
|
+
NODE
|
|
178
|
+
|
|
179
|
+
if [[ -x "./scripts/release-smoke.sh" ]]; then
|
|
180
|
+
echo "== extension-specific smoke checks (scripts/release-smoke.sh)"
|
|
181
|
+
NPM_CONFIG_PREFIX="$TEST_NPM_PREFIX" NPM_CONFIG_CACHE="$TEST_NPM_CACHE" \
|
|
182
|
+
PI_CODING_AGENT_DIR="$TEST_AGENT_DIR" PACKAGE_SPEC="$PACKAGE_SPEC" bash ./scripts/release-smoke.sh
|
|
183
|
+
fi
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
echo "== npm view ${NAME} version (pre-publish may be 404)"
|
|
187
|
+
set +e
|
|
188
|
+
npm view "$NAME" version --json --registry https://registry.npmjs.org/
|
|
189
|
+
VIEW_EXIT=$?
|
|
190
|
+
set -e
|
|
191
|
+
echo "npm view exit: $VIEW_EXIT"
|
|
192
|
+
if [[ "$VIEW_EXIT" -ne 0 ]]; then
|
|
193
|
+
echo "Package likely not published yet (expected for first release)."
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
echo "release-check done"
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
|
|
6
|
+
export const readJsonFile = (filePath) => JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
7
|
+
|
|
8
|
+
export const packageSourcesFromSettings = (settings) => {
|
|
9
|
+
const packages = Array.isArray(settings?.packages) ? settings.packages : [];
|
|
10
|
+
return packages
|
|
11
|
+
.map((entry) => {
|
|
12
|
+
if (typeof entry === "string") return entry;
|
|
13
|
+
if (entry && typeof entry === "object" && typeof entry.source === "string") {
|
|
14
|
+
return entry.source;
|
|
15
|
+
}
|
|
16
|
+
return undefined;
|
|
17
|
+
})
|
|
18
|
+
.filter(Boolean);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const findPackageEntryBySource = (settings, packageSpec) => {
|
|
22
|
+
const packages = Array.isArray(settings?.packages) ? settings.packages : [];
|
|
23
|
+
return packages.find((entry) => {
|
|
24
|
+
if (typeof entry === "string") return entry === packageSpec;
|
|
25
|
+
return entry && typeof entry === "object" && entry.source === packageSpec;
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const assertPackageSpecInstalled = ({ settings, packageSpec }) => {
|
|
30
|
+
if (!packageSpec || typeof packageSpec !== "string") {
|
|
31
|
+
throw new Error("PACKAGE_SPEC is required for release smoke validation.");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const entry = findPackageEntryBySource(settings, packageSpec);
|
|
35
|
+
if (!entry) {
|
|
36
|
+
const sources = packageSourcesFromSettings(settings);
|
|
37
|
+
throw new Error(
|
|
38
|
+
[
|
|
39
|
+
`Installed package spec not found in isolated pi settings: ${packageSpec}`,
|
|
40
|
+
`Found: ${sources.join(", ") || "(none)"}`,
|
|
41
|
+
].join("\n"),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (typeof entry !== "string") {
|
|
46
|
+
throw new Error(
|
|
47
|
+
"Release smoke requires an unfiltered package entry; object-form package filters could disable the extension under test.",
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const normalizePathForOutput = (value) => path.resolve(value).replace(/\\/g, "/");
|
|
53
|
+
const normalizeOutput = (value) => String(value || "").replace(/\\/g, "/");
|
|
54
|
+
|
|
55
|
+
export const assertInstalledArtifactPackage = ({ packageRoot, packageName, packageVersion }) => {
|
|
56
|
+
if (!packageRoot || typeof packageRoot !== "string") {
|
|
57
|
+
throw new Error("packageRoot is required for release smoke validation.");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const packageJsonPath = path.join(packageRoot, "package.json");
|
|
61
|
+
const extensionPath = path.join(packageRoot, "extensions", "agent-vent.ts");
|
|
62
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
63
|
+
throw new Error(`Installed package.json not found: ${packageJsonPath}`);
|
|
64
|
+
}
|
|
65
|
+
if (!fs.existsSync(extensionPath)) {
|
|
66
|
+
throw new Error(`Installed agent_vent extension not found: ${extensionPath}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const pkg = readJsonFile(packageJsonPath);
|
|
70
|
+
if (pkg.name !== packageName) {
|
|
71
|
+
throw new Error(`Installed package name mismatch: expected ${packageName}, got ${pkg.name}`);
|
|
72
|
+
}
|
|
73
|
+
if (pkg.version !== packageVersion) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`Installed package version mismatch: expected ${packageVersion}, got ${pkg.version}`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
const extensions = Array.isArray(pkg.pi?.extensions) ? pkg.pi.extensions : [];
|
|
79
|
+
if (!extensions.includes("./extensions/agent-vent.ts")) {
|
|
80
|
+
throw new Error("Installed package pi.extensions does not include ./extensions/agent-vent.ts");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return { packageJsonPath, extensionPath };
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const assertLocalTarballInstallSource = ({ packageSpec }) => {
|
|
87
|
+
if (!packageSpec || typeof packageSpec !== "string") {
|
|
88
|
+
throw new Error("PACKAGE_SPEC is required for local tarball install-source validation.");
|
|
89
|
+
}
|
|
90
|
+
if (!packageSpec.startsWith("npm:")) {
|
|
91
|
+
throw new Error(`Release smoke expected an npm: tarball install source, got: ${packageSpec}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const tarballPath = packageSpec.slice("npm:".length);
|
|
95
|
+
if (!path.isAbsolute(tarballPath) || !tarballPath.endsWith(".tgz")) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Release smoke expected npm:<absolute .tgz path> as the install source, got: ${packageSpec}`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { tarballPath };
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export const buildLocalPathArtifactSettings = ({ settings, packageRoot }) => {
|
|
105
|
+
if (!packageRoot || typeof packageRoot !== "string") {
|
|
106
|
+
throw new Error("packageRoot is required for release smoke settings preparation.");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
...settings,
|
|
111
|
+
packages: [packageRoot],
|
|
112
|
+
extensions: [],
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const buildInstalledArtifactSettings = buildLocalPathArtifactSettings;
|
|
117
|
+
|
|
118
|
+
export const assertAgentVentPathSmokeOutput = ({ output, ventDir }) => {
|
|
119
|
+
if (!ventDir || typeof ventDir !== "string") {
|
|
120
|
+
throw new Error("ventDir is required for release smoke validation.");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const normalizedOutput = normalizeOutput(output);
|
|
124
|
+
const normalizedVentDir = normalizePathForOutput(ventDir);
|
|
125
|
+
const expectedStore = `${normalizedVentDir}/vents.jsonl`;
|
|
126
|
+
const expectedReview = `${normalizedVentDir}/review-events.jsonl`;
|
|
127
|
+
const expectedCuration = `${normalizedVentDir}/curation-events.jsonl`;
|
|
128
|
+
const expectedRetention = `${normalizedVentDir}/retention-events.jsonl`;
|
|
129
|
+
const expectedBackups = `${normalizedVentDir}/backups`;
|
|
130
|
+
|
|
131
|
+
const requiredFragments = [
|
|
132
|
+
`Agent vent store: ${expectedStore}`,
|
|
133
|
+
`Agent vent review events: ${expectedReview}`,
|
|
134
|
+
`Agent vent curation events: ${expectedCuration}`,
|
|
135
|
+
`Agent vent retention events: ${expectedRetention}`,
|
|
136
|
+
`Agent vent retention backups: ${expectedBackups}`,
|
|
137
|
+
"Override: set PI_AGENT_VENT_DIR",
|
|
138
|
+
"not tasks, issues, incidents, evidence, telemetry, or ASC/self state",
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
const missing = requiredFragments.filter((fragment) => !normalizedOutput.includes(fragment));
|
|
142
|
+
if (missing.length) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
[
|
|
145
|
+
"Installed /agent_vent path smoke output did not match the expected local diagnostic contract.",
|
|
146
|
+
...missing.map((fragment) => `Missing: ${fragment}`),
|
|
147
|
+
].join("\n"),
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const defaultStoreFragment = "/.pi/agent/agent-vent/vents.jsonl";
|
|
152
|
+
if (normalizedOutput.includes(defaultStoreFragment)) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
"Installed /agent_vent path smoke used the default operator vent store instead of the isolated PI_AGENT_VENT_DIR.",
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const activeStoreArtifacts = (ventDir) => [
|
|
160
|
+
path.join(ventDir, "vents.jsonl"),
|
|
161
|
+
path.join(ventDir, "review-events.jsonl"),
|
|
162
|
+
path.join(ventDir, "curation-events.jsonl"),
|
|
163
|
+
path.join(ventDir, "retention-events.jsonl"),
|
|
164
|
+
path.join(ventDir, "backups"),
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
const findNodeModulesAncestor = (startPath) => {
|
|
168
|
+
let current = path.resolve(startPath);
|
|
169
|
+
while (current && current !== path.dirname(current)) {
|
|
170
|
+
if (path.basename(current) === "node_modules") return current;
|
|
171
|
+
current = path.dirname(current);
|
|
172
|
+
}
|
|
173
|
+
return undefined;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const createImportablePackageShadow = (packageRoot) => {
|
|
177
|
+
const resolvedPackageRoot = path.resolve(packageRoot);
|
|
178
|
+
const shadowParent = fs.mkdtempSync(path.join(os.tmpdir(), "agent-vent-installed-shadow-"));
|
|
179
|
+
const shadowRoot = path.join(shadowParent, "package");
|
|
180
|
+
fs.cpSync(resolvedPackageRoot, shadowRoot, {
|
|
181
|
+
recursive: true,
|
|
182
|
+
filter: (sourcePath) => {
|
|
183
|
+
const relativePath = path.relative(resolvedPackageRoot, sourcePath);
|
|
184
|
+
return !relativePath.split(path.sep).includes("node_modules");
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const packageNodeModules = path.join(packageRoot, "node_modules");
|
|
189
|
+
const dependencyRoot = fs.existsSync(packageNodeModules)
|
|
190
|
+
? packageNodeModules
|
|
191
|
+
: findNodeModulesAncestor(packageRoot);
|
|
192
|
+
if (dependencyRoot) {
|
|
193
|
+
fs.symlinkSync(dependencyRoot, path.join(shadowRoot, "node_modules"), "dir");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return { shadowParent, shadowRoot };
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export const executeInstalledArtifactToolPathSmoke = async ({ packageRoot, ventDir }) => {
|
|
200
|
+
if (!packageRoot || typeof packageRoot !== "string") {
|
|
201
|
+
throw new Error("packageRoot is required for installed tool smoke validation.");
|
|
202
|
+
}
|
|
203
|
+
if (!ventDir || typeof ventDir !== "string") {
|
|
204
|
+
throw new Error("ventDir is required for installed tool smoke validation.");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const installedExtensionPath = path.join(packageRoot, "extensions", "agent-vent.ts");
|
|
208
|
+
if (!fs.existsSync(installedExtensionPath)) {
|
|
209
|
+
throw new Error(`Installed agent_vent extension not found: ${installedExtensionPath}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const tools = new Map();
|
|
213
|
+
const commands = new Map();
|
|
214
|
+
const shadow = createImportablePackageShadow(packageRoot);
|
|
215
|
+
try {
|
|
216
|
+
const extensionPath = path.join(shadow.shadowRoot, "extensions", "agent-vent.ts");
|
|
217
|
+
const extensionModule = await import(pathToFileURL(extensionPath).href);
|
|
218
|
+
if (typeof extensionModule.default !== "function") {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`Installed agent_vent extension has no default factory: ${installedExtensionPath}`,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
extensionModule.default({
|
|
225
|
+
registerTool(tool) {
|
|
226
|
+
if (tool?.name) tools.set(tool.name, tool);
|
|
227
|
+
},
|
|
228
|
+
registerCommand(name, command) {
|
|
229
|
+
commands.set(name, command);
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
} finally {
|
|
233
|
+
fs.rmSync(shadow.shadowParent, { recursive: true, force: true });
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const tool = tools.get("agent_vent");
|
|
237
|
+
if (!tool || typeof tool.execute !== "function") {
|
|
238
|
+
throw new Error("Installed agent_vent extension did not register executable agent_vent tool.");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const artifacts = activeStoreArtifacts(ventDir);
|
|
242
|
+
const existingArtifacts = new Set(
|
|
243
|
+
artifacts.filter((artifactPath) => fs.existsSync(artifactPath)),
|
|
244
|
+
);
|
|
245
|
+
const oldVentDir = process.env.PI_AGENT_VENT_DIR;
|
|
246
|
+
let result;
|
|
247
|
+
try {
|
|
248
|
+
process.env.PI_AGENT_VENT_DIR = ventDir;
|
|
249
|
+
result = await tool.execute(
|
|
250
|
+
"release-smoke-tool-path",
|
|
251
|
+
{ action: "path" },
|
|
252
|
+
undefined,
|
|
253
|
+
undefined,
|
|
254
|
+
{
|
|
255
|
+
cwd: packageRoot,
|
|
256
|
+
sessionManager: { getSessionFile: () => undefined },
|
|
257
|
+
},
|
|
258
|
+
);
|
|
259
|
+
} finally {
|
|
260
|
+
if (oldVentDir === undefined) delete process.env.PI_AGENT_VENT_DIR;
|
|
261
|
+
else process.env.PI_AGENT_VENT_DIR = oldVentDir;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const output = result?.content?.[0]?.text;
|
|
265
|
+
if (typeof output !== "string") {
|
|
266
|
+
throw new Error("Installed agent_vent tool path smoke did not return text content.");
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
assertAgentVentPathSmokeOutput({ output, ventDir });
|
|
270
|
+
|
|
271
|
+
const createdArtifacts = artifacts.filter(
|
|
272
|
+
(artifactPath) => !existingArtifacts.has(artifactPath) && fs.existsSync(artifactPath),
|
|
273
|
+
);
|
|
274
|
+
if (createdArtifacts.length) {
|
|
275
|
+
throw new Error(
|
|
276
|
+
[
|
|
277
|
+
"Installed agent_vent tool path smoke created local store artifacts; action=path must stay no-store-read/no-store-write.",
|
|
278
|
+
...createdArtifacts.map((artifactPath) => `Created: ${artifactPath}`),
|
|
279
|
+
].join("\n"),
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return { output, executionMode: "shadow-copy", registeredCommandCount: commands.size };
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const readArgValue = (args, name) => {
|
|
287
|
+
const index = args.indexOf(name);
|
|
288
|
+
if (index === -1) return undefined;
|
|
289
|
+
return args[index + 1];
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const runCli = async () => {
|
|
293
|
+
const [command, ...args] = process.argv.slice(2);
|
|
294
|
+
|
|
295
|
+
if (command === "assert-settings") {
|
|
296
|
+
const settingsPath = readArgValue(args, "--settings");
|
|
297
|
+
const packageSpec = readArgValue(args, "--package-spec");
|
|
298
|
+
if (!settingsPath) throw new Error("--settings is required");
|
|
299
|
+
assertPackageSpecInstalled({ settings: readJsonFile(settingsPath), packageSpec });
|
|
300
|
+
console.log("Isolated pi settings include installed package spec.");
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (command === "assert-installed-artifact") {
|
|
305
|
+
const packageRoot = readArgValue(args, "--package-root");
|
|
306
|
+
const packageName = readArgValue(args, "--package-name");
|
|
307
|
+
const packageVersion = readArgValue(args, "--package-version");
|
|
308
|
+
assertInstalledArtifactPackage({ packageRoot, packageName, packageVersion });
|
|
309
|
+
console.log("Installed package artifact exposes the agent_vent extension entrypoint.");
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (command === "assert-local-tarball-install-source") {
|
|
314
|
+
const packageSpec = readArgValue(args, "--package-spec");
|
|
315
|
+
assertLocalTarballInstallSource({ packageSpec });
|
|
316
|
+
console.log(
|
|
317
|
+
"Local npm:<tarball> package spec is validated as an install source; runtime discovery smoke uses the installed local package path.",
|
|
318
|
+
);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (
|
|
323
|
+
command === "prepare-local-path-artifact-settings" ||
|
|
324
|
+
command === "prepare-installed-artifact-settings"
|
|
325
|
+
) {
|
|
326
|
+
const settingsPath = readArgValue(args, "--settings");
|
|
327
|
+
const packageRoot = readArgValue(args, "--package-root");
|
|
328
|
+
if (!settingsPath) throw new Error("--settings is required");
|
|
329
|
+
const settings = readJsonFile(settingsPath);
|
|
330
|
+
const nextSettings = buildLocalPathArtifactSettings({ settings, packageRoot });
|
|
331
|
+
fs.writeFileSync(settingsPath, `${JSON.stringify(nextSettings, null, 2)}\n`, "utf8");
|
|
332
|
+
console.log(
|
|
333
|
+
"Isolated pi settings now load the installed package artifact through local-path package discovery.",
|
|
334
|
+
);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (command === "assert-command-output") {
|
|
339
|
+
const outputPath = readArgValue(args, "--output");
|
|
340
|
+
const ventDir = readArgValue(args, "--vent-dir");
|
|
341
|
+
if (!outputPath) throw new Error("--output is required");
|
|
342
|
+
assertAgentVentPathSmokeOutput({ output: fs.readFileSync(outputPath, "utf8"), ventDir });
|
|
343
|
+
console.log("Installed /agent_vent path command smoke output OK.");
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (command === "assert-installed-tool-path") {
|
|
348
|
+
const packageRoot = readArgValue(args, "--package-root");
|
|
349
|
+
const ventDir = readArgValue(args, "--vent-dir");
|
|
350
|
+
await executeInstalledArtifactToolPathSmoke({ packageRoot, ventDir });
|
|
351
|
+
console.log("Installed artifact shadow registered-tool path smoke output OK.");
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
throw new Error(
|
|
356
|
+
"Usage: node ./scripts/release-smoke-check.mjs <assert-settings|assert-local-tarball-install-source|assert-installed-artifact|prepare-local-path-artifact-settings|assert-command-output|assert-installed-tool-path> ...",
|
|
357
|
+
);
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
361
|
+
try {
|
|
362
|
+
await runCli();
|
|
363
|
+
} catch (error) {
|
|
364
|
+
console.error(error.message);
|
|
365
|
+
process.exit(1);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
cd "$ROOT_DIR"
|
|
6
|
+
|
|
7
|
+
if [[ -z "${PACKAGE_SPEC:-}" ]]; then
|
|
8
|
+
echo "PACKAGE_SPEC is required; release-check.sh sets it to the packed tarball spec." >&2
|
|
9
|
+
exit 1
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
if [[ -z "${PI_CODING_AGENT_DIR:-}" ]]; then
|
|
13
|
+
echo "PI_CODING_AGENT_DIR is required so release smoke cannot touch operator pi settings." >&2
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
if ! command -v pi >/dev/null 2>&1; then
|
|
18
|
+
echo "pi CLI not found in PATH." >&2
|
|
19
|
+
exit 1
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
if [[ ! -f "$PI_CODING_AGENT_DIR/settings.json" ]]; then
|
|
23
|
+
echo "Isolated pi settings missing: $PI_CODING_AGENT_DIR/settings.json" >&2
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
SMOKE_DIR=""
|
|
28
|
+
cleanup() {
|
|
29
|
+
if [[ "${KEEP_RELEASE_ARTIFACTS:-0}" != "1" && -n "$SMOKE_DIR" && -d "$SMOKE_DIR" ]]; then
|
|
30
|
+
rm -rf "$SMOKE_DIR"
|
|
31
|
+
fi
|
|
32
|
+
}
|
|
33
|
+
trap cleanup EXIT
|
|
34
|
+
|
|
35
|
+
SMOKE_DIR="$(mktemp -d /tmp/pi-agent-vent-release-smoke-XXXXXX)"
|
|
36
|
+
SMOKE_LOCAL_PATH_VENT_DIR="$SMOKE_DIR/agent-vent-local-path-store"
|
|
37
|
+
SMOKE_TOOL_VENT_DIR="$SMOKE_DIR/agent-vent-tool-store"
|
|
38
|
+
SMOKE_LOCAL_PATH_OUTPUT="$SMOKE_DIR/agent-vent-local-path.out"
|
|
39
|
+
PACKAGE_NAME="$(node -p "JSON.parse(require('node:fs').readFileSync('package.json', 'utf8')).name")"
|
|
40
|
+
PACKAGE_VERSION="$(node -p "JSON.parse(require('node:fs').readFileSync('package.json', 'utf8')).version")"
|
|
41
|
+
if [[ -n "${NPM_CONFIG_PREFIX:-}" ]]; then
|
|
42
|
+
GLOBAL_NODE_MODULES="$(npm --prefix "$NPM_CONFIG_PREFIX" root -g)"
|
|
43
|
+
else
|
|
44
|
+
GLOBAL_NODE_MODULES="$(npm root -g)"
|
|
45
|
+
fi
|
|
46
|
+
INSTALLED_PACKAGE_ROOT="$GLOBAL_NODE_MODULES/$PACKAGE_NAME"
|
|
47
|
+
INSTALLED_EXTENSION_PATH="$INSTALLED_PACKAGE_ROOT/extensions/agent-vent.ts"
|
|
48
|
+
|
|
49
|
+
if [[ -n "${NPM_CONFIG_PREFIX:-}" ]]; then
|
|
50
|
+
case "$INSTALLED_PACKAGE_ROOT" in
|
|
51
|
+
"$NPM_CONFIG_PREFIX"/*) ;;
|
|
52
|
+
*)
|
|
53
|
+
echo "Installed package root escaped isolated npm prefix: $INSTALLED_PACKAGE_ROOT" >&2
|
|
54
|
+
exit 1
|
|
55
|
+
;;
|
|
56
|
+
esac
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
node ./scripts/release-smoke-check.mjs assert-settings \
|
|
60
|
+
--settings "$PI_CODING_AGENT_DIR/settings.json" \
|
|
61
|
+
--package-spec "$PACKAGE_SPEC"
|
|
62
|
+
|
|
63
|
+
node ./scripts/release-smoke-check.mjs assert-installed-artifact \
|
|
64
|
+
--package-root "$INSTALLED_PACKAGE_ROOT" \
|
|
65
|
+
--package-name "$PACKAGE_NAME" \
|
|
66
|
+
--package-version "$PACKAGE_VERSION"
|
|
67
|
+
|
|
68
|
+
node ./scripts/release-smoke-check.mjs assert-local-tarball-install-source \
|
|
69
|
+
--package-spec "$PACKAGE_SPEC"
|
|
70
|
+
|
|
71
|
+
node ./scripts/release-smoke-check.mjs prepare-local-path-artifact-settings \
|
|
72
|
+
--settings "$PI_CODING_AGENT_DIR/settings.json" \
|
|
73
|
+
--package-root "$INSTALLED_PACKAGE_ROOT"
|
|
74
|
+
|
|
75
|
+
echo "== installed local-path package-discovery /agent_vent command smoke"
|
|
76
|
+
PI_AGENT_VENT_DIR="$SMOKE_LOCAL_PATH_VENT_DIR" \
|
|
77
|
+
pi --offline --no-session --no-builtin-tools --no-skills --no-prompt-templates --no-context-files --no-themes \
|
|
78
|
+
-p "/agent_vent path" >"$SMOKE_LOCAL_PATH_OUTPUT" 2>&1
|
|
79
|
+
cat "$SMOKE_LOCAL_PATH_OUTPUT"
|
|
80
|
+
|
|
81
|
+
node ./scripts/release-smoke-check.mjs assert-command-output \
|
|
82
|
+
--output "$SMOKE_LOCAL_PATH_OUTPUT" \
|
|
83
|
+
--vent-dir "$SMOKE_LOCAL_PATH_VENT_DIR"
|
|
84
|
+
|
|
85
|
+
echo "== installed artifact shadow agent_vent registered-tool path smoke"
|
|
86
|
+
node ./scripts/release-smoke-check.mjs assert-installed-tool-path \
|
|
87
|
+
--package-root "$INSTALLED_PACKAGE_ROOT" \
|
|
88
|
+
--vent-dir "$SMOKE_TOOL_VENT_DIR"
|
|
89
|
+
|
|
90
|
+
echo "release smoke done: local npm:<tarball> install source was validated; installed artifact /agent_vent path command loads through local-path package discovery and agent_vent shadow tool path executes from $INSTALLED_PACKAGE_ROOT."
|