@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.
@@ -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."