@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,248 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
|
|
5
|
+
let failed = false;
|
|
6
|
+
const errors = [];
|
|
7
|
+
|
|
8
|
+
function fail(msg) {
|
|
9
|
+
errors.push(msg);
|
|
10
|
+
failed = true;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function readJsonSafe(filePath) {
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
16
|
+
} catch {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function readTextSafe(filePath) {
|
|
22
|
+
try {
|
|
23
|
+
return fs.readFileSync(filePath, "utf8");
|
|
24
|
+
} catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readCopierAnswer(key) {
|
|
30
|
+
const answers = readTextSafe(".copier-answers.yml");
|
|
31
|
+
if (!answers) return undefined;
|
|
32
|
+
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
33
|
+
const match = new RegExp(`^${escaped}:\\s*['"]?([^'"\\r\\n]+)['"]?\\s*$`, "m").exec(answers);
|
|
34
|
+
return match?.[1]?.trim();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function expectedReleaseConfigMode() {
|
|
38
|
+
return readCopierAnswer("release_config_mode") ?? "component";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function validateScopedPackageName(p) {
|
|
42
|
+
const npmOrg = readCopierAnswer("npm_org");
|
|
43
|
+
const repoName = readCopierAnswer("repo_name");
|
|
44
|
+
if (!npmOrg || !repoName) {
|
|
45
|
+
fail(".copier-answers.yml must include npm_org and repo_name for package name validation");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const expectedName = `@${npmOrg.replace(/^@/, "")}/${repoName}`;
|
|
50
|
+
if (p.name !== expectedName) {
|
|
51
|
+
fail(`package.json name must be scoped npm identity '${expectedName}' (got '${p.name}')`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function validatePackageJson() {
|
|
56
|
+
const p = readJsonSafe("package.json");
|
|
57
|
+
if (!p) {
|
|
58
|
+
fail("Failed to parse package.json");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
validateScopedPackageName(p);
|
|
63
|
+
|
|
64
|
+
if (!Array.isArray(p.keywords) || !p.keywords.includes("pi-package")) {
|
|
65
|
+
fail("package.json missing keywords entry: pi-package");
|
|
66
|
+
}
|
|
67
|
+
if (!Array.isArray(p.keywords) || !p.keywords.includes("pi-extension")) {
|
|
68
|
+
fail("package.json missing keywords entry: pi-extension");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const ext = p.pi?.extensions;
|
|
72
|
+
if (!Array.isArray(ext) || ext.length < 1) {
|
|
73
|
+
fail("package.json missing pi.extensions array");
|
|
74
|
+
} else {
|
|
75
|
+
for (const entry of ext) {
|
|
76
|
+
const normalized = entry.replace(/^\.\//, "");
|
|
77
|
+
if (!fs.existsSync(normalized)) {
|
|
78
|
+
fail(`pi.extensions entry does not exist: ${entry}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const prompts = p.pi?.prompts;
|
|
84
|
+
if (!Array.isArray(prompts) || prompts.length < 1) {
|
|
85
|
+
fail("package.json missing pi.prompts array");
|
|
86
|
+
} else {
|
|
87
|
+
for (const entry of prompts) {
|
|
88
|
+
const normalized = entry.replace(/\/$/, "").replace(/^\.\//, "");
|
|
89
|
+
if (!fs.existsSync(normalized)) {
|
|
90
|
+
fail(`pi.prompts entry does not exist: ${entry}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const requiredPeers = ["@mariozechner/pi-coding-agent", "@mariozechner/pi-ai"];
|
|
96
|
+
for (const peer of requiredPeers) {
|
|
97
|
+
if (typeof p.peerDependencies?.[peer] !== "string") {
|
|
98
|
+
fail(`package.json peerDependencies must include ${peer}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const scriptExpectations = {
|
|
103
|
+
fix: "bash ./scripts/quality-gate.sh fix",
|
|
104
|
+
lint: "bash ./scripts/quality-gate.sh lint",
|
|
105
|
+
typecheck: "bash ./scripts/quality-gate.sh typecheck",
|
|
106
|
+
"quality:pre-commit": "bash ./scripts/quality-gate.sh pre-commit",
|
|
107
|
+
"quality:pre-push": "bash ./scripts/quality-gate.sh pre-push",
|
|
108
|
+
"quality:ci": "bash ./scripts/quality-gate.sh ci",
|
|
109
|
+
check: "npm run quality:ci",
|
|
110
|
+
test: "npm run quality:ci",
|
|
111
|
+
"docs:list": "bash ./scripts/docs-list.sh",
|
|
112
|
+
"docs:list:workspace": "bash ./scripts/docs-list.sh --workspace --discover",
|
|
113
|
+
"docs:list:json": "bash ./scripts/docs-list.sh --json",
|
|
114
|
+
"release:check": "bash ./scripts/release-check.sh",
|
|
115
|
+
"release:check:quick": "SKIP_PI_SMOKE=1 bash ./scripts/release-check.sh",
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
for (const [scriptName, expected] of Object.entries(scriptExpectations)) {
|
|
119
|
+
if (p.scripts?.[scriptName] !== expected) {
|
|
120
|
+
fail(`package.json scripts.${scriptName} must be '${expected}'`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (p.publishConfig?.registry !== "https://registry.npmjs.org/") {
|
|
125
|
+
fail("package.json publishConfig.registry must be 'https://registry.npmjs.org/'");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (p.publishConfig?.access !== "public") {
|
|
129
|
+
fail("package.json publishConfig.access must be 'public'");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (p.engines?.node !== ">=22") {
|
|
133
|
+
fail("package.json engines.node must be '>=22'");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const expectedWorkspacePath = readCopierAnswer("workspace_relative_path");
|
|
137
|
+
if (typeof p.repository?.directory !== "string" || p.repository.directory.length === 0) {
|
|
138
|
+
fail("package.json repository.directory must be set for monorepo package mode");
|
|
139
|
+
} else if (expectedWorkspacePath && p.repository.directory !== expectedWorkspacePath) {
|
|
140
|
+
fail(
|
|
141
|
+
`package.json repository.directory must match .copier-answers.yml workspace_relative_path '${expectedWorkspacePath}' (got '${p.repository.directory}')`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const templateMeta = p["x-pi-template"];
|
|
146
|
+
if (!templateMeta || typeof templateMeta !== "object") {
|
|
147
|
+
fail("package.json must include x-pi-template metadata");
|
|
148
|
+
} else {
|
|
149
|
+
if (!["simple-package", "monorepo-package"].includes(templateMeta.scaffoldMode)) {
|
|
150
|
+
fail(
|
|
151
|
+
"package.json x-pi-template.scaffoldMode must be 'simple-package' or legacy alias 'monorepo-package'",
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
const expectedReleaseComponent = readCopierAnswer("release_component_key");
|
|
155
|
+
if (typeof templateMeta.workspacePath !== "string" || templateMeta.workspacePath.length === 0) {
|
|
156
|
+
fail("package.json x-pi-template.workspacePath must be non-empty");
|
|
157
|
+
} else if (expectedWorkspacePath && templateMeta.workspacePath !== expectedWorkspacePath) {
|
|
158
|
+
fail(
|
|
159
|
+
`package.json x-pi-template.workspacePath must match .copier-answers.yml workspace_relative_path '${expectedWorkspacePath}' (got '${templateMeta.workspacePath}')`,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
if (
|
|
163
|
+
typeof templateMeta.releaseComponent !== "string" ||
|
|
164
|
+
templateMeta.releaseComponent.length === 0
|
|
165
|
+
) {
|
|
166
|
+
fail("package.json x-pi-template.releaseComponent must be non-empty");
|
|
167
|
+
} else if (
|
|
168
|
+
expectedReleaseComponent &&
|
|
169
|
+
templateMeta.releaseComponent !== expectedReleaseComponent
|
|
170
|
+
) {
|
|
171
|
+
fail(
|
|
172
|
+
`package.json x-pi-template.releaseComponent must match .copier-answers.yml release_component_key '${expectedReleaseComponent}' (got '${templateMeta.releaseComponent}')`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
if (!["component", "none"].includes(templateMeta.releaseConfigMode)) {
|
|
176
|
+
fail("package.json x-pi-template.releaseConfigMode must be 'component' or 'none'");
|
|
177
|
+
}
|
|
178
|
+
const expectedMode = expectedReleaseConfigMode();
|
|
179
|
+
if (templateMeta.releaseConfigMode !== expectedMode) {
|
|
180
|
+
fail(
|
|
181
|
+
`package.json x-pi-template.releaseConfigMode must match .copier-answers.yml release_config_mode '${expectedMode}' (got '${templateMeta.releaseConfigMode}')`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const biomeVersion = p.devDependencies?.["@biomejs/biome"];
|
|
187
|
+
if (typeof biomeVersion !== "string") {
|
|
188
|
+
fail("package.json devDependencies must include @biomejs/biome");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!Array.isArray(p.files) || p.files.length < 1) {
|
|
192
|
+
fail("package.json must define a non-empty files array");
|
|
193
|
+
} else {
|
|
194
|
+
if (!p.files.includes("prompts")) {
|
|
195
|
+
fail("package.json files must include 'prompts'");
|
|
196
|
+
}
|
|
197
|
+
if (!p.files.includes("examples")) {
|
|
198
|
+
fail("package.json files must include 'examples'");
|
|
199
|
+
}
|
|
200
|
+
if (!p.files.includes("policy/security-policy.json")) {
|
|
201
|
+
fail("package.json files must include 'policy/security-policy.json'");
|
|
202
|
+
}
|
|
203
|
+
if (!p.files.includes("policy/engineering-lane.json")) {
|
|
204
|
+
fail("package.json files must include 'policy/engineering-lane.json'");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
for (const entry of ext) {
|
|
208
|
+
const normalized = entry.replace(/^\.\//, "");
|
|
209
|
+
if (!p.files.includes(normalized)) {
|
|
210
|
+
fail(`package.json files must include extension artifact: ${normalized}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function validateEngineeringLane() {
|
|
217
|
+
const stackLane = readJsonSafe("policy/engineering-lane.json");
|
|
218
|
+
if (!stackLane) {
|
|
219
|
+
fail("Failed to parse policy/engineering-lane.json");
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (stackLane.lane !== "ts") {
|
|
224
|
+
fail("policy/engineering-lane.json lane must be 'ts'");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const laneName = stackLane.engineering_core?.lane;
|
|
228
|
+
if (laneName !== "pi-ts") {
|
|
229
|
+
fail("policy/engineering-lane.json engineering_core.lane must be 'pi-ts'");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function main() {
|
|
234
|
+
validatePackageJson();
|
|
235
|
+
validateEngineeringLane();
|
|
236
|
+
|
|
237
|
+
if (failed) {
|
|
238
|
+
for (const error of errors) {
|
|
239
|
+
console.error(error);
|
|
240
|
+
}
|
|
241
|
+
console.error(`Validation failed with ${errors.length} issue(s).`);
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
console.log("Node.js validation passed.");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
main();
|
|
@@ -0,0 +1,145 @@
|
|
|
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
|
+
required_files=(
|
|
8
|
+
"README.md"
|
|
9
|
+
"LICENSE"
|
|
10
|
+
"AGENTS.md"
|
|
11
|
+
"CHANGELOG.md"
|
|
12
|
+
"biome.jsonc"
|
|
13
|
+
".vscode/settings.json"
|
|
14
|
+
".copier-answers.yml"
|
|
15
|
+
"package.json"
|
|
16
|
+
"next_session_prompt.md"
|
|
17
|
+
"docs/org/operating_model.md"
|
|
18
|
+
"docs/project/foundation.md"
|
|
19
|
+
"docs/project/vision.md"
|
|
20
|
+
"docs/project/incentives.md"
|
|
21
|
+
"docs/project/resources.md"
|
|
22
|
+
"docs/project/contributing.md"
|
|
23
|
+
"docs/project/extension-sop.md"
|
|
24
|
+
"docs/project/trusted-publishing.md"
|
|
25
|
+
"docs/engineering.local.md"
|
|
26
|
+
"docs/adr/.gitkeep"
|
|
27
|
+
"policy/engineering-lane.json"
|
|
28
|
+
"policy/security-policy.json"
|
|
29
|
+
".pi/prompts/commit.md"
|
|
30
|
+
"scripts/docs-list.sh"
|
|
31
|
+
"scripts/release-artifact-check.mjs"
|
|
32
|
+
"scripts/release-check.sh"
|
|
33
|
+
"scripts/validate-structure.sh"
|
|
34
|
+
"scripts/validate-structure.mjs"
|
|
35
|
+
"scripts/quality-gate.sh"
|
|
36
|
+
"prompts/implementation-planning.md"
|
|
37
|
+
"prompts/security-review.md"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
required_dirs=(
|
|
41
|
+
".vscode"
|
|
42
|
+
"docs"
|
|
43
|
+
"docs/adr"
|
|
44
|
+
"docs/org"
|
|
45
|
+
"docs/project"
|
|
46
|
+
"extensions"
|
|
47
|
+
"examples"
|
|
48
|
+
"external"
|
|
49
|
+
"ontology"
|
|
50
|
+
"policy"
|
|
51
|
+
"scripts"
|
|
52
|
+
"src"
|
|
53
|
+
"tests"
|
|
54
|
+
".pi"
|
|
55
|
+
".pi/prompts"
|
|
56
|
+
"prompts"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
required_executables=(
|
|
60
|
+
"scripts/docs-list.sh"
|
|
61
|
+
"scripts/release-check.sh"
|
|
62
|
+
"scripts/validate-structure.sh"
|
|
63
|
+
"scripts/validate-structure.mjs"
|
|
64
|
+
"scripts/quality-gate.sh"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
errors=0
|
|
68
|
+
|
|
69
|
+
for required_file in "${required_files[@]}"; do
|
|
70
|
+
if [[ ! -f "$required_file" ]]; then
|
|
71
|
+
echo "Missing required file: $required_file" >&2
|
|
72
|
+
((errors+=1))
|
|
73
|
+
fi
|
|
74
|
+
done
|
|
75
|
+
|
|
76
|
+
for required_dir in "${required_dirs[@]}"; do
|
|
77
|
+
if [[ ! -d "$required_dir" ]]; then
|
|
78
|
+
echo "Missing required directory: $required_dir" >&2
|
|
79
|
+
((errors+=1))
|
|
80
|
+
fi
|
|
81
|
+
done
|
|
82
|
+
|
|
83
|
+
for executable in "${required_executables[@]}"; do
|
|
84
|
+
if [[ ! -x "$executable" ]]; then
|
|
85
|
+
echo "Expected executable bit on: $executable" >&2
|
|
86
|
+
((errors+=1))
|
|
87
|
+
fi
|
|
88
|
+
done
|
|
89
|
+
|
|
90
|
+
for copier_key in "_src_path:" "repo_name:" "command_name:" "scaffold_mode:" "workspace_relative_path:" "release_component_key:" "release_config_mode:"; do
|
|
91
|
+
if ! grep -q "^${copier_key}" ".copier-answers.yml"; then
|
|
92
|
+
echo "Missing copier answer key in .copier-answers.yml: ${copier_key}" >&2
|
|
93
|
+
((errors+=1))
|
|
94
|
+
fi
|
|
95
|
+
done
|
|
96
|
+
|
|
97
|
+
if [[ -d ".github" ]]; then
|
|
98
|
+
echo "Monorepo package scaffold must not create package-local .github directory" >&2
|
|
99
|
+
((errors+=1))
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
if [[ -d ".githooks" ]]; then
|
|
103
|
+
echo "Monorepo package scaffold must not create package-local .githooks directory" >&2
|
|
104
|
+
((errors+=1))
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
if ! grep -q '^\*\.tgz$' ".gitignore"; then
|
|
108
|
+
echo ".gitignore must ignore npm tarball outputs (*.tgz)" >&2
|
|
109
|
+
((errors+=1))
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
if command -v node >/dev/null 2>&1; then
|
|
113
|
+
if ! node "$ROOT_DIR/scripts/validate-structure.mjs"; then
|
|
114
|
+
((errors+=1))
|
|
115
|
+
fi
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
while IFS= read -r -d '' markdown_file; do
|
|
119
|
+
if [[ "$(head -n 1 "$markdown_file")" != "---" ]]; then
|
|
120
|
+
echo "Missing YAML frontmatter start in: $markdown_file" >&2
|
|
121
|
+
((errors+=1))
|
|
122
|
+
continue
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
if ! grep -q "^system4d:" "$markdown_file"; then
|
|
126
|
+
echo "Missing system4d section in: $markdown_file" >&2
|
|
127
|
+
((errors+=1))
|
|
128
|
+
continue
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
for key in container compass engine fog; do
|
|
132
|
+
if ! grep -q "^ $key:" "$markdown_file"; then
|
|
133
|
+
echo "Missing system4d.$key in: $markdown_file" >&2
|
|
134
|
+
((errors+=1))
|
|
135
|
+
fi
|
|
136
|
+
done
|
|
137
|
+
|
|
138
|
+
done < <(find . -type f -name "*.md" ! -path "./node_modules/*" ! -path "./.git/*" -print0)
|
|
139
|
+
|
|
140
|
+
if [[ "$errors" -gt 0 ]]; then
|
|
141
|
+
echo "Structure validation failed with $errors issue(s)." >&2
|
|
142
|
+
exit 1
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
echo "Structure validation passed."
|