@piagent/platform 1.0.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/.gitattributes +7 -0
- package/AGENTS.md +31 -0
- package/CHANGELOG.md +296 -0
- package/CODE_OF_CONDUCT.md +36 -0
- package/CONTRIBUTING.md +66 -0
- package/LICENSE +21 -0
- package/README.md +588 -0
- package/SECURITY.md +38 -0
- package/adapters/backend-api/profile.json +88 -0
- package/adapters/be-readonly-fe/profile.json +120 -0
- package/adapters/data/profile.json +88 -0
- package/adapters/devops/profile.json +92 -0
- package/adapters/docs/profile.json +74 -0
- package/adapters/fullstack/profile.json +94 -0
- package/adapters/generic/profile.json +95 -0
- package/adapters/mobile/profile.json +87 -0
- package/adapters/node-typescript/profile.json +76 -0
- package/adapters/python/profile.json +75 -0
- package/adapters/web-frontend/profile.json +101 -0
- package/catalog/capabilities.json +206 -0
- package/docs/architecture.md +64 -0
- package/docs/auto-delegation-policy.md +192 -0
- package/docs/capability-packs.md +234 -0
- package/docs/command-reference-vietnamese.md +484 -0
- package/docs/context-window-policy.md +85 -0
- package/docs/distribution-standard.md +229 -0
- package/docs/herdr-workflow.md +58 -0
- package/docs/mcp-and-tools.md +316 -0
- package/docs/memory-policy.md +164 -0
- package/docs/model-options.md +165 -0
- package/docs/oauth-providers.md +53 -0
- package/docs/operator-manual-vietnamese.md +1080 -0
- package/docs/package-architecture-notes.md +101 -0
- package/docs/project-adapters.md +174 -0
- package/docs/project-onboarding.md +135 -0
- package/docs/publishing-for-teams.md +64 -0
- package/docs/quality-benchmark.md +74 -0
- package/docs/quickstart-vietnamese.md +350 -0
- package/docs/release-install-policy.md +223 -0
- package/docs/runtime-harness-standard.md +118 -0
- package/docs/runtime-policy-design.md +132 -0
- package/docs/runtime-quality-baseline.md +257 -0
- package/docs/security-redaction-benchmark.md +56 -0
- package/docs/security-threat-model.md +66 -0
- package/docs/subagent-orchestration-capabilities.md +104 -0
- package/docs/subagents-and-multiagent.md +346 -0
- package/docs/task-implementation-contract.md +200 -0
- package/docs/team-onboarding.md +333 -0
- package/docs/usage-observability.md +130 -0
- package/docs/vercel-docs-site.md +115 -0
- package/docs/workflow-recipes.md +125 -0
- package/docs-site/assets/piagent-logo.svg +35 -0
- package/docs-site/favicon.svg +18 -0
- package/docs-site/index.html +1613 -0
- package/docs-site/vercel.json +24 -0
- package/evals/fixtures/action-proposal.invalid.json +34 -0
- package/evals/fixtures/action-proposal.valid.json +34 -0
- package/evals/fixtures/capability-pack.invalid.json +123 -0
- package/evals/fixtures/capability-pack.valid.json +123 -0
- package/evals/fixtures/capability-recipe.invalid.json +81 -0
- package/evals/fixtures/capability-recipe.valid.json +82 -0
- package/evals/fixtures/context-index.invalid.json +25 -0
- package/evals/fixtures/context-index.valid.json +25 -0
- package/evals/fixtures/eval-scenario.invalid.json +40 -0
- package/evals/fixtures/eval-scenario.valid.json +45 -0
- package/evals/fixtures/project-profile.invalid.json +88 -0
- package/evals/fixtures/project-profile.valid.json +95 -0
- package/evals/fixtures/task-contract.invalid.json +73 -0
- package/evals/fixtures/task-contract.valid.json +74 -0
- package/evals/golden/enforcement-decisions.json +329 -0
- package/evals/scenarios/capability-resolution.json +45 -0
- package/package.json +132 -0
- package/packages/piagent-core/README.md +147 -0
- package/packages/piagent-core/capabilities/capability-core.js +1023 -0
- package/packages/piagent-core/capabilities/capability-sources.js +265 -0
- package/packages/piagent-core/extensions/guard-io.js +45 -0
- package/packages/piagent-core/extensions/guard-shell-analysis.ts +558 -0
- package/packages/piagent-core/extensions/guard-types.ts +406 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +5760 -0
- package/packages/piagent-core/extensions/policy-core.js +1070 -0
- package/packages/piagent-core/extensions/redaction-core.js +4 -0
- package/packages/piagent-core/extensions/runtime-evidence.js +215 -0
- package/packages/piagent-core/package.json +46 -0
- package/packages/piagent-core/policies/base-policy.json +249 -0
- package/packages/piagent-core/prompts/be-to-fe.md +82 -0
- package/packages/piagent-core/prompts/commit.md +27 -0
- package/packages/piagent-core/prompts/discuss.md +35 -0
- package/packages/piagent-core/prompts/memory-policy.md +52 -0
- package/packages/piagent-core/prompts/model-options.md +68 -0
- package/packages/piagent-core/prompts/onboard-project.md +147 -0
- package/packages/piagent-core/prompts/piagent-commands.md +68 -0
- package/packages/piagent-core/prompts/plan.md +35 -0
- package/packages/piagent-core/prompts/platform-improve.md +69 -0
- package/packages/piagent-core/prompts/pr.md +26 -0
- package/packages/piagent-core/prompts/review.md +38 -0
- package/packages/piagent-core/prompts/scout.md +50 -0
- package/packages/piagent-core/prompts/task.md +69 -0
- package/packages/piagent-core/security/sensitive-data.js +187 -0
- package/packages/piagent-core/skills/piagent-ops/SKILL.md +44 -0
- package/packages/piagent-core/skills/piagent-source-cache/SKILL.md +59 -0
- package/packages/piagent-core/skills/piagent-source-cache/checkout-source-repo.sh +144 -0
- package/packages/piagent-core/subagents/piagent-oracle.md +29 -0
- package/packages/piagent-core/subagents/piagent-planner.md +46 -0
- package/packages/piagent-core/subagents/piagent-reviewer.md +48 -0
- package/packages/piagent-core/subagents/piagent-scout.md +37 -0
- package/packages/piagent-core/subagents/piagent-worker.md +44 -0
- package/packs/engineering-base/pack.json +123 -0
- package/packs/engineering-base/recipes/bounded-change.json +82 -0
- package/packs/web-delivery/pack.json +72 -0
- package/packs/web-delivery/recipes/verified-web-change.json +57 -0
- package/schemas/action-proposal.schema.json +90 -0
- package/schemas/capability-pack.schema.json +190 -0
- package/schemas/capability-recipe.schema.json +72 -0
- package/schemas/context-index.schema.json +258 -0
- package/schemas/eval-scenario.schema.json +67 -0
- package/schemas/project-profile.schema.json +555 -0
- package/schemas/task-contract.schema.json +314 -0
- package/scripts/audit-runtime-host.sh +78 -0
- package/scripts/bootstrap-local.sh +25 -0
- package/scripts/capability-catalog.mjs +290 -0
- package/scripts/check-runtime-advisories.mjs +114 -0
- package/scripts/configure-mcp.sh +301 -0
- package/scripts/configure-model-scope.sh +143 -0
- package/scripts/configure-subagents.sh +241 -0
- package/scripts/import-agent-instructions.mjs +199 -0
- package/scripts/init-project.sh +369 -0
- package/scripts/install-global.sh +532 -0
- package/scripts/link-project.sh +25 -0
- package/scripts/migrate-project-state.mjs +176 -0
- package/scripts/pi-auto.sh +79 -0
- package/scripts/pi-model-catalog.sh +154 -0
- package/scripts/pi-session-stats.sh +135 -0
- package/scripts/piagent-cli.mjs +53 -0
- package/scripts/profile-doctor.sh +238 -0
- package/scripts/quality-benchmark.sh +185 -0
- package/scripts/redaction-benchmark.mjs +227 -0
- package/scripts/runtime-policy-smoke.sh +48 -0
- package/scripts/setup.sh +425 -0
- package/scripts/team-doctor.sh +461 -0
- package/scripts/verify-local.sh +540 -0
- package/scripts/verify-release-identity.mjs +85 -0
- package/scripts/verify-vercel-link.mjs +64 -0
- package/templates/global/mcp.json +9 -0
- package/templates/global/settings.json +37 -0
- package/templates/project/.mcp.json +9 -0
- package/templates/project/.pi/context-index.json +25 -0
- package/templates/project/.pi/gitignore.template +22 -0
- package/templates/project/.pi/mcp.json +9 -0
- package/templates/project/.pi/memory/MEMORY.md +11 -0
- package/templates/project/.pi/memory/memory_summary.md +9 -0
- package/templates/project/.pi/piagent-profile.json +107 -0
- package/templates/project/.pi/project-context.md +30 -0
- package/templates/project/.pi/settings.json +17 -0
- package/templates/project/.pi/task-contract.template.json +74 -0
- package/templates/project/.pi/tech-context/README.md +7 -0
- package/templates/project/.pi/tech-stack.json +10 -0
- package/templates/project/AGENTS.md +34 -0
- package/templates/project/REVIEW_GUIDELINES.md +23 -0
- package/types/pi-runtime-shims.d.ts +13 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
usage() {
|
|
5
|
+
cat <<'USAGE'
|
|
6
|
+
Usage:
|
|
7
|
+
scripts/team-doctor.sh [project-path] [--strict-share]
|
|
8
|
+
|
|
9
|
+
Checks:
|
|
10
|
+
- Pi/Herdr availability
|
|
11
|
+
- Root package Pi manifest
|
|
12
|
+
- MCP adapter/baseline visibility
|
|
13
|
+
- Subagents package/config visibility
|
|
14
|
+
- Foreign skill directories wired into .pi/settings.json skills
|
|
15
|
+
- Agent instruction files this platform does not read
|
|
16
|
+
- Project .pi/settings.json package source
|
|
17
|
+
- Project piagent profile
|
|
18
|
+
- No local-machine paths in share-critical files when --strict-share is used
|
|
19
|
+
USAGE
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
PROJECT_PATH=""
|
|
23
|
+
STRICT_SHARE=false
|
|
24
|
+
|
|
25
|
+
while [[ $# -gt 0 ]]; do
|
|
26
|
+
case "$1" in
|
|
27
|
+
--strict-share)
|
|
28
|
+
STRICT_SHARE=true
|
|
29
|
+
shift
|
|
30
|
+
;;
|
|
31
|
+
-h|--help)
|
|
32
|
+
usage
|
|
33
|
+
exit 0
|
|
34
|
+
;;
|
|
35
|
+
*)
|
|
36
|
+
if [[ -z "$PROJECT_PATH" ]]; then
|
|
37
|
+
PROJECT_PATH="$1"
|
|
38
|
+
shift
|
|
39
|
+
else
|
|
40
|
+
echo "Unknown argument: $1" >&2
|
|
41
|
+
usage >&2
|
|
42
|
+
exit 2
|
|
43
|
+
fi
|
|
44
|
+
;;
|
|
45
|
+
esac
|
|
46
|
+
done
|
|
47
|
+
|
|
48
|
+
PLATFORM_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
49
|
+
PROJECT_PATH="${PROJECT_PATH:-$PWD}"
|
|
50
|
+
|
|
51
|
+
node --input-type=module - "$PLATFORM_ROOT" "$PROJECT_PATH" "$STRICT_SHARE" <<'NODE'
|
|
52
|
+
import fs from "node:fs";
|
|
53
|
+
import path from "node:path";
|
|
54
|
+
import { spawnSync } from "node:child_process";
|
|
55
|
+
import { pathToFileURL } from "node:url";
|
|
56
|
+
|
|
57
|
+
const [platformRoot, projectPath, strictShareRaw] = process.argv.slice(2);
|
|
58
|
+
const strictShare = strictShareRaw === "true";
|
|
59
|
+
const errors = [];
|
|
60
|
+
const warnings = [];
|
|
61
|
+
const { verifyCapabilityLock } = await import(pathToFileURL(path.join(platformRoot, "packages", "piagent-core", "capabilities", "capability-core.js")).href);
|
|
62
|
+
|
|
63
|
+
function exists(rel) {
|
|
64
|
+
return fs.existsSync(path.join(platformRoot, rel));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function readJson(file) {
|
|
68
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function commandExists(command) {
|
|
72
|
+
const result = spawnSync("sh", ["-lc", `command -v ${command} >/dev/null 2>&1`], { stdio: "ignore" });
|
|
73
|
+
return result.status === 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function isWslRuntime() {
|
|
77
|
+
if (process.platform !== "linux") return false;
|
|
78
|
+
try {
|
|
79
|
+
return /microsoft|wsl/i.test(fs.readFileSync("/proc/version", "utf8"));
|
|
80
|
+
} catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function runtimeMatrixStatus() {
|
|
86
|
+
const platform = process.platform;
|
|
87
|
+
const arch = process.arch;
|
|
88
|
+
if (platform === "darwin" && arch === "arm64") {
|
|
89
|
+
return {
|
|
90
|
+
surface: "macos-apple-silicon/darwin-arm64",
|
|
91
|
+
status: "verified",
|
|
92
|
+
teamRolloutReady: true,
|
|
93
|
+
note: "Apple Silicon macOS with Bash is in the current supported matrix."
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
if (platform === "darwin" && arch === "x64") {
|
|
97
|
+
return {
|
|
98
|
+
surface: "macos-intel/darwin-x64",
|
|
99
|
+
status: "supported-target",
|
|
100
|
+
teamRolloutReady: false,
|
|
101
|
+
note: "Node.js is available for this target, but this release has not been gated on an Intel Mac runner."
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (platform === "linux" && isWslRuntime()) {
|
|
105
|
+
return {
|
|
106
|
+
surface: `wsl2/${arch}`,
|
|
107
|
+
status: "experimental",
|
|
108
|
+
teamRolloutReady: false,
|
|
109
|
+
note: "WSL2 has Linux-like Node/Bash behavior, but it is not part of the release gate."
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (platform === "linux" && arch === "x64") {
|
|
113
|
+
return {
|
|
114
|
+
surface: "linux-x64",
|
|
115
|
+
status: "verified-ci",
|
|
116
|
+
teamRolloutReady: true,
|
|
117
|
+
note: "Ubuntu x64 with Bash is covered by CI release gates."
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
if (platform === "linux" && arch === "arm64") {
|
|
121
|
+
return {
|
|
122
|
+
surface: "linux-arm64",
|
|
123
|
+
status: "supported-target",
|
|
124
|
+
teamRolloutReady: false,
|
|
125
|
+
note: "Node.js is available for this target, but this release has not been gated on Linux ARM64."
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (platform === "win32") {
|
|
129
|
+
return {
|
|
130
|
+
surface: `native-windows/${arch}`,
|
|
131
|
+
status: "not-supported-for-team-rollout",
|
|
132
|
+
teamRolloutReady: false,
|
|
133
|
+
note: "The Node runtime is available, but platform helper scripts and shell policy rely on Bash/POSIX semantics."
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
surface: `${platform}/${arch}`,
|
|
138
|
+
status: "outside-release-matrix",
|
|
139
|
+
teamRolloutReady: false,
|
|
140
|
+
note: "This runtime is outside the documented v1.0.0 support matrix."
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const runtime = runtimeMatrixStatus();
|
|
145
|
+
if (!runtime.teamRolloutReady) {
|
|
146
|
+
warnings.push(`runtime surface ${runtime.surface} is ${runtime.status}: ${runtime.note}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
for (const rel of [
|
|
150
|
+
"package.json",
|
|
151
|
+
"packages/piagent-core/extensions/piagent-guard.ts",
|
|
152
|
+
"packages/piagent-core/prompts/onboard-project.md",
|
|
153
|
+
"packages/piagent-core/prompts/memory-policy.md",
|
|
154
|
+
"packages/piagent-core/prompts/platform-improve.md",
|
|
155
|
+
"packages/piagent-core/prompts/be-to-fe.md",
|
|
156
|
+
"packages/piagent-core/prompts/task.md",
|
|
157
|
+
"packages/piagent-core/prompts/discuss.md",
|
|
158
|
+
"packages/piagent-core/subagents/piagent-scout.md",
|
|
159
|
+
"packages/piagent-core/subagents/piagent-planner.md",
|
|
160
|
+
"packages/piagent-core/subagents/piagent-worker.md",
|
|
161
|
+
"packages/piagent-core/subagents/piagent-reviewer.md",
|
|
162
|
+
"packages/piagent-core/subagents/piagent-oracle.md",
|
|
163
|
+
"templates/project/.pi/settings.json",
|
|
164
|
+
"templates/project/.pi/piagent-profile.json",
|
|
165
|
+
"templates/project/.mcp.json",
|
|
166
|
+
"templates/project/.pi/mcp.json",
|
|
167
|
+
"templates/project/.pi/project-context.md",
|
|
168
|
+
"templates/project/.pi/context-index.json",
|
|
169
|
+
"templates/project/.pi/memory/memory_summary.md",
|
|
170
|
+
"templates/project/.pi/memory/MEMORY.md",
|
|
171
|
+
"scripts/setup.sh",
|
|
172
|
+
"scripts/configure-mcp.sh",
|
|
173
|
+
"scripts/configure-subagents.sh"
|
|
174
|
+
]) {
|
|
175
|
+
if (!exists(rel)) errors.push(`missing platform file: ${rel}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const rootPackage = readJson(path.join(platformRoot, "package.json"));
|
|
179
|
+
if (!rootPackage.pi?.extensions?.length) errors.push("root package.json missing pi.extensions");
|
|
180
|
+
if (!rootPackage.pi?.skills?.length) errors.push("root package.json missing pi.skills");
|
|
181
|
+
if (!rootPackage.pi?.prompts?.length) errors.push("root package.json missing pi.prompts");
|
|
182
|
+
if (!rootPackage.pi?.subagents?.agents?.length) errors.push("root package.json missing pi.subagents.agents");
|
|
183
|
+
|
|
184
|
+
if (!commandExists("pi")) warnings.push("pi is not on PATH");
|
|
185
|
+
if (!commandExists("herdr")) warnings.push("herdr is not on PATH; Herdr integration optional");
|
|
186
|
+
|
|
187
|
+
function readJsonIfPresent(file) {
|
|
188
|
+
if (!fs.existsSync(file)) return null;
|
|
189
|
+
try {
|
|
190
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
191
|
+
} catch (error) {
|
|
192
|
+
warnings.push(`cannot parse JSON: ${file}`);
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function profileOptionalArrayOrEmpty(profile, field, profileLabel) {
|
|
198
|
+
if (profile[field] === undefined) return [];
|
|
199
|
+
if (!Array.isArray(profile[field])) {
|
|
200
|
+
errors.push(`${profileLabel} ${field} must be array when provided`);
|
|
201
|
+
return [];
|
|
202
|
+
}
|
|
203
|
+
return profile[field];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function warnShellOnlyProtectedPaths(profile, profileLabel) {
|
|
207
|
+
const protectedPaths = Array.isArray(profile.protectedPaths) ? profile.protectedPaths : [];
|
|
208
|
+
const shellProtectedPaths = profileOptionalArrayOrEmpty(profile, "shellProtectedPaths", profileLabel);
|
|
209
|
+
const readOnlyPaths = profileOptionalArrayOrEmpty(profile, "readOnlyPaths", profileLabel);
|
|
210
|
+
const writeGuardedPaths = new Set([...protectedPaths, ...readOnlyPaths]);
|
|
211
|
+
for (const candidate of shellProtectedPaths) {
|
|
212
|
+
if (typeof candidate !== "string" || candidate.trim().length === 0) continue;
|
|
213
|
+
if (writeGuardedPaths.has(candidate)) continue;
|
|
214
|
+
warnings.push(`${profileLabel} shellProtectedPaths-only path ${candidate} blocks shell access only; add it to protectedPaths to block read/write, or readOnlyPaths to allow read but block write/shell`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function mcpSummary(file) {
|
|
219
|
+
const json = readJsonIfPresent(file);
|
|
220
|
+
if (!json) return { file, exists: fs.existsSync(file), serverCount: 0, servers: [] };
|
|
221
|
+
const mcpServers = json.mcpServers && typeof json.mcpServers === "object" && !Array.isArray(json.mcpServers)
|
|
222
|
+
? json.mcpServers
|
|
223
|
+
: {};
|
|
224
|
+
return { file, exists: true, serverCount: Object.keys(mcpServers).length, servers: Object.keys(mcpServers).sort() };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
let piHasMcpAdapter = false;
|
|
228
|
+
let piHasSubagents = false;
|
|
229
|
+
if (commandExists("pi")) {
|
|
230
|
+
const piList = spawnSync("pi", ["list"], { encoding: "utf8" });
|
|
231
|
+
const combined = `${piList.stdout ?? ""}\n${piList.stderr ?? ""}`;
|
|
232
|
+
piHasMcpAdapter = combined.includes("pi-mcp-adapter");
|
|
233
|
+
piHasSubagents = combined.includes("pi-subagents");
|
|
234
|
+
if (!piHasMcpAdapter) warnings.push("Pi MCP adapter not found in `pi list`; run setup with --with-mcp or `pi install npm:pi-mcp-adapter@2.11.0`");
|
|
235
|
+
if (!piHasSubagents) warnings.push("Pi subagents package not found in `pi list`; run setup with --with-subagents or `pi install npm:pi-subagents@0.35.1`");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const mcpFiles = [
|
|
239
|
+
path.join(process.env.XDG_CONFIG_HOME || path.join(process.env.HOME || "", ".config"), "mcp", "mcp.json"),
|
|
240
|
+
path.join(process.env.PI_CODING_AGENT_DIR || path.join(process.env.HOME || "", ".pi", "agent"), "mcp.json"),
|
|
241
|
+
path.join(projectPath, ".mcp.json"),
|
|
242
|
+
path.join(projectPath, ".pi", "mcp.json")
|
|
243
|
+
];
|
|
244
|
+
const mcp = mcpFiles.map(mcpSummary);
|
|
245
|
+
const totalMcpServers = mcp.reduce((sum, item) => sum + item.serverCount, 0);
|
|
246
|
+
if (piHasMcpAdapter && totalMcpServers === 0) {
|
|
247
|
+
warnings.push("Pi MCP adapter is installed but no MCP servers are configured; run `piagent-mcp --preset core --scope global` or `/mcp setup`");
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const subagentConfigPath = path.join(process.env.PI_CODING_AGENT_DIR || path.join(process.env.HOME || "", ".pi", "agent"), "extensions", "subagent", "config.json");
|
|
251
|
+
const subagentConfig = readJsonIfPresent(subagentConfigPath);
|
|
252
|
+
if (piHasSubagents && !subagentConfig) {
|
|
253
|
+
warnings.push("Pi subagents package is installed but config is missing; run `piagent-subagents --preset safe`");
|
|
254
|
+
}
|
|
255
|
+
if (subagentConfig && subagentConfig.toolDescriptionMode !== "compact") {
|
|
256
|
+
warnings.push("Pi subagents toolDescriptionMode is not compact; token use may be higher");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const projectSettingsPath = path.join(projectPath, ".pi", "settings.json");
|
|
260
|
+
let projectPackageSource = "workspace";
|
|
261
|
+
let projectSkillPaths = [];
|
|
262
|
+
if (fs.existsSync(projectSettingsPath)) {
|
|
263
|
+
const settings = readJson(projectSettingsPath);
|
|
264
|
+
projectSkillPaths = Array.isArray(settings.skills) ? settings.skills.filter((entry) => typeof entry === "string") : [];
|
|
265
|
+
const packages = Array.isArray(settings.packages) ? settings.packages : [];
|
|
266
|
+
if (packages.length === 0) errors.push("project .pi/settings.json has no packages");
|
|
267
|
+
const declaredSource = packages.find((source) => typeof source === "string" && source.length > 0);
|
|
268
|
+
if (declaredSource) projectPackageSource = declaredSource;
|
|
269
|
+
for (const source of packages) {
|
|
270
|
+
if (typeof source === "string" && source.includes("__PIAGENT_PACKAGE_SOURCE__")) {
|
|
271
|
+
errors.push("project .pi/settings.json still has package source placeholder");
|
|
272
|
+
}
|
|
273
|
+
if (strictShare && typeof source === "string" && source.startsWith("/")) {
|
|
274
|
+
warnings.push(`project package source is absolute path, not team-portable: ${source}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
warnings.push("project has no .pi/settings.json");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Pi already scans ~/.pi/agent/skills, ~/.agents/skills, .pi/skills, .agents/skills,
|
|
282
|
+
// and package.json entries. Only foreign-agent locations need an explicit settings entry,
|
|
283
|
+
// so re-declaring the native ones would only produce name-collision warnings.
|
|
284
|
+
function countSkillDirs(dir) {
|
|
285
|
+
if (!fs.existsSync(dir)) return 0;
|
|
286
|
+
try {
|
|
287
|
+
return fs.readdirSync(dir, { withFileTypes: true })
|
|
288
|
+
.filter((entry) => entry.isDirectory() && fs.existsSync(path.join(dir, entry.name, "SKILL.md")))
|
|
289
|
+
.length;
|
|
290
|
+
} catch {
|
|
291
|
+
return 0;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const skillWiring = [".claude/skills", ".codex/skills"].map((rel) => {
|
|
296
|
+
const absolute = path.join(projectPath, rel);
|
|
297
|
+
const skillCount = countSkillDirs(absolute);
|
|
298
|
+
const wired = projectSkillPaths.includes(rel);
|
|
299
|
+
if (skillCount > 0 && !wired) {
|
|
300
|
+
warnings.push(`${skillCount} skill(s) exist in ${rel} but it is not listed in .pi/settings.json skills; Pi will not load them`);
|
|
301
|
+
}
|
|
302
|
+
return { path: rel, exists: fs.existsSync(absolute), skillCount, wired };
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Instruction files written for other agents. Onboarding reads AGENTS.md and README.md,
|
|
306
|
+
// so these are currently ignored; report them instead of skipping silently.
|
|
307
|
+
const foreignAgentConfig = ["CLAUDE.md", ".claude/rules", ".claude/agents", ".cursor/rules", ".github/copilot-instructions.md"]
|
|
308
|
+
.filter((rel) => fs.existsSync(path.join(projectPath, rel)));
|
|
309
|
+
if (foreignAgentConfig.length > 0) {
|
|
310
|
+
warnings.push(`project has agent instruction files this platform does not read: ${foreignAgentConfig.join(", ")}; onboarding uses AGENTS.md, so their rules are not enforced`);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const projectProfilePath = path.join(projectPath, ".pi", "piagent-profile.json");
|
|
314
|
+
let projectProfile = null;
|
|
315
|
+
if (fs.existsSync(projectProfilePath)) {
|
|
316
|
+
const profile = readJson(projectProfilePath);
|
|
317
|
+
projectProfile = profile;
|
|
318
|
+
for (const field of ["schemaVersion", "projectId", "displayName", "mode"]) {
|
|
319
|
+
if (profile[field] === undefined || profile[field] === "") errors.push(`project profile missing ${field}`);
|
|
320
|
+
}
|
|
321
|
+
for (const field of ["rootMarkers", "protectedPaths", "requiredContext", "mcpCapabilities"]) {
|
|
322
|
+
if (!Array.isArray(profile[field])) errors.push(`project profile ${field} must be array`);
|
|
323
|
+
}
|
|
324
|
+
warnShellOnlyProtectedPaths(profile, "project profile");
|
|
325
|
+
if (Array.isArray(profile.requiredContext) && !profile.requiredContext.includes(".pi/project-context.md")) {
|
|
326
|
+
warnings.push("project profile does not require .pi/project-context.md");
|
|
327
|
+
}
|
|
328
|
+
if (Array.isArray(profile.capabilityPacks)) {
|
|
329
|
+
const projectLockPath = path.join(projectPath, ".pi", "piagent-profile.lock.json");
|
|
330
|
+
if (!fs.existsSync(projectLockPath)) {
|
|
331
|
+
errors.push("project capability lock is missing");
|
|
332
|
+
} else {
|
|
333
|
+
try {
|
|
334
|
+
const lock = readJson(projectLockPath);
|
|
335
|
+
const verification = verifyCapabilityLock(platformRoot, projectProfilePath, lock, { packageSource: projectPackageSource });
|
|
336
|
+
if (!verification.ok) errors.push("project capability lock is stale or does not match the configured package source");
|
|
337
|
+
} catch (error) {
|
|
338
|
+
errors.push(`project capability lock validation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
} else {
|
|
342
|
+
warnings.push("project profile uses the legacy capability contract; apply a current profile to create a capability lock");
|
|
343
|
+
}
|
|
344
|
+
} else {
|
|
345
|
+
warnings.push("project has no .pi/piagent-profile.json");
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const projectContextPath = path.join(projectPath, ".pi", "project-context.md");
|
|
349
|
+
if (fs.existsSync(projectContextPath)) {
|
|
350
|
+
const projectContext = fs.readFileSync(projectContextPath, "utf8");
|
|
351
|
+
if (/Generated:\s*not yet/i.test(projectContext)) {
|
|
352
|
+
warnings.push("project onboarding snapshot is still pending; run /onboard-project in Pi after login/model selection");
|
|
353
|
+
}
|
|
354
|
+
} else {
|
|
355
|
+
warnings.push("project has no .pi/project-context.md; run setup/init or /onboard-project");
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const contextIndexConfig = projectProfile && typeof projectProfile.contextIndex === "object" && !Array.isArray(projectProfile.contextIndex)
|
|
359
|
+
? projectProfile.contextIndex
|
|
360
|
+
: {};
|
|
361
|
+
const contextIndexEnabled = contextIndexConfig.enabled !== false && contextIndexConfig.writePolicy !== "off";
|
|
362
|
+
const contextIndexRelativePath = typeof contextIndexConfig.path === "string" && contextIndexConfig.path.trim()
|
|
363
|
+
? contextIndexConfig.path.trim()
|
|
364
|
+
: ".pi/context-index.json";
|
|
365
|
+
const contextIndexAbsolutePath = path.resolve(projectPath, contextIndexRelativePath);
|
|
366
|
+
const contextIndexEscapes = path.relative(projectPath, contextIndexAbsolutePath).startsWith("..") || path.isAbsolute(path.relative(projectPath, contextIndexAbsolutePath));
|
|
367
|
+
if (contextIndexEscapes) {
|
|
368
|
+
errors.push(`project contextIndex.path escapes project root: ${contextIndexRelativePath}`);
|
|
369
|
+
} else if (!contextIndexEnabled) {
|
|
370
|
+
warnings.push("project context index is disabled by profile");
|
|
371
|
+
} else if (fs.existsSync(contextIndexAbsolutePath)) {
|
|
372
|
+
const contextIndex = readJsonIfPresent(contextIndexAbsolutePath);
|
|
373
|
+
if (!contextIndex) {
|
|
374
|
+
warnings.push(`project context index exists but cannot be parsed: ${contextIndexRelativePath}`);
|
|
375
|
+
} else {
|
|
376
|
+
const nodes = Array.isArray(contextIndex.nodes) ? contextIndex.nodes : [];
|
|
377
|
+
const edges = Array.isArray(contextIndex.edges) ? contextIndex.edges : [];
|
|
378
|
+
const citations = Array.isArray(contextIndex.citations) ? contextIndex.citations : [];
|
|
379
|
+
if (contextIndex.schemaVersion !== 1) warnings.push("project context index schemaVersion should be 1");
|
|
380
|
+
if (contextIndex.summary === "Pending. Run /onboard-project after login/model selection to generate the compact project context index.") {
|
|
381
|
+
warnings.push("project context index is still pending; run /onboard-project after login/model selection");
|
|
382
|
+
}
|
|
383
|
+
if (nodes.length === 0) warnings.push("project context index has no nodes yet");
|
|
384
|
+
if (contextIndex?.policy?.requireCitations !== false && citations.length === 0) warnings.push("project context index has no citations yet");
|
|
385
|
+
if (nodes.length > (contextIndex?.policy?.maxNodes ?? 120)) warnings.push("project context index has more nodes than policy maxNodes");
|
|
386
|
+
if (edges.length > (contextIndex?.policy?.maxEdges ?? 240)) warnings.push("project context index has more edges than policy maxEdges");
|
|
387
|
+
}
|
|
388
|
+
} else {
|
|
389
|
+
warnings.push(`project has no ${contextIndexRelativePath}; run setup/init or /onboard-project`);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const memorySummaryPath = path.join(projectPath, ".pi", "memory", "memory_summary.md");
|
|
393
|
+
const memoryHandbookPath = path.join(projectPath, ".pi", "memory", "MEMORY.md");
|
|
394
|
+
if (!fs.existsSync(memorySummaryPath) || !fs.existsSync(memoryHandbookPath)) {
|
|
395
|
+
warnings.push("project has no .pi/memory scaffold; run setup/init or use /memory-policy before relying on project memory");
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (strictShare) {
|
|
399
|
+
const shareCriticalFiles = [
|
|
400
|
+
"README.md",
|
|
401
|
+
"docs/quickstart-vietnamese.md",
|
|
402
|
+
"docs/team-onboarding.md",
|
|
403
|
+
"docs/project-onboarding.md",
|
|
404
|
+
"docs/workflow-recipes.md",
|
|
405
|
+
"docs/memory-policy.md",
|
|
406
|
+
"docs/context-window-policy.md",
|
|
407
|
+
"docs/publishing-for-teams.md",
|
|
408
|
+
"templates/project/.pi/settings.json",
|
|
409
|
+
"templates/project/.pi/context-index.json",
|
|
410
|
+
"templates/global/settings.json",
|
|
411
|
+
"packages/piagent-core/skills/piagent-source-cache/SKILL.md"
|
|
412
|
+
];
|
|
413
|
+
const forbidden = [
|
|
414
|
+
/\/Users\/[^/\s]+\/Documents\/Working\b/,
|
|
415
|
+
/Documents\/Working/
|
|
416
|
+
];
|
|
417
|
+
for (const rel of shareCriticalFiles) {
|
|
418
|
+
const file = path.join(platformRoot, rel);
|
|
419
|
+
if (!fs.existsSync(file)) continue;
|
|
420
|
+
const text = fs.readFileSync(file, "utf8");
|
|
421
|
+
for (const pattern of forbidden) {
|
|
422
|
+
if (pattern.test(text)) {
|
|
423
|
+
errors.push(`share-critical file has local/default-forbidden reference ${pattern}: ${rel}`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const report = {
|
|
430
|
+
platformRoot,
|
|
431
|
+
projectPath,
|
|
432
|
+
strictShare,
|
|
433
|
+
runtime: {
|
|
434
|
+
node: process.version,
|
|
435
|
+
platform: process.platform,
|
|
436
|
+
arch: process.arch,
|
|
437
|
+
surface: runtime.surface,
|
|
438
|
+
status: runtime.status,
|
|
439
|
+
teamRolloutReady: runtime.teamRolloutReady,
|
|
440
|
+
note: runtime.note
|
|
441
|
+
},
|
|
442
|
+
piOnPath: commandExists("pi"),
|
|
443
|
+
herdrOnPath: commandExists("herdr"),
|
|
444
|
+
piHasMcpAdapter,
|
|
445
|
+
piHasSubagents,
|
|
446
|
+
mcp,
|
|
447
|
+
skillWiring,
|
|
448
|
+
foreignAgentConfig,
|
|
449
|
+
subagents: {
|
|
450
|
+
configPath: subagentConfigPath,
|
|
451
|
+
configExists: Boolean(subagentConfig),
|
|
452
|
+
config: subagentConfig
|
|
453
|
+
},
|
|
454
|
+
rootPiManifest: rootPackage.pi,
|
|
455
|
+
warnings,
|
|
456
|
+
errors
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
console.log(JSON.stringify(report, null, 2));
|
|
460
|
+
if (errors.length > 0) process.exit(1);
|
|
461
|
+
NODE
|