@piagent/platform 1.1.9 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +175 -1
- package/README.md +2 -2
- package/docs/command-reference-vietnamese.md +19 -5
- package/docs/distribution-standard.md +7 -7
- package/docs/mcp-and-tools.md +154 -2
- package/docs/model-options.md +10 -8
- package/docs/operator-manual-vietnamese.md +25 -13
- package/docs/publishing-for-teams.md +2 -0
- package/docs/quickstart-vietnamese.md +2 -2
- package/docs/release-install-policy.md +6 -6
- package/docs/security-threat-model.md +4 -3
- package/docs/team-onboarding.md +4 -4
- package/docs/usage-observability.md +29 -0
- package/docs/vercel-docs-site.md +34 -4
- package/docs-site/architecture.html +152 -0
- package/docs-site/assets/docs.css +1001 -0
- package/docs-site/assets/docs.js +84 -0
- package/docs-site/commands.html +203 -0
- package/docs-site/content/architecture.html +29 -0
- package/docs-site/content/commands.html +80 -0
- package/docs-site/content/index.html +80 -0
- package/docs-site/content/mcp-approval.html +133 -0
- package/docs-site/content/mcp-auth.html +76 -0
- package/docs-site/content/mcp-servers.html +111 -0
- package/docs-site/content/mcp.html +120 -0
- package/docs-site/content/permissions.html +54 -0
- package/docs-site/content/profiles.html +68 -0
- package/docs-site/content/quickstart.html +144 -0
- package/docs-site/content/runtime.html +45 -0
- package/docs-site/content/scope.html +71 -0
- package/docs-site/content/security.html +51 -0
- package/docs-site/content/team.html +23 -0
- package/docs-site/content/workflows.html +47 -0
- package/docs-site/index.html +131 -1534
- package/docs-site/mcp-approval.html +256 -0
- package/docs-site/mcp-auth.html +199 -0
- package/docs-site/mcp-servers.html +234 -0
- package/docs-site/mcp.html +243 -0
- package/docs-site/permissions.html +177 -0
- package/docs-site/profiles.html +191 -0
- package/docs-site/quickstart.html +267 -0
- package/docs-site/runtime.html +168 -0
- package/docs-site/scope.html +194 -0
- package/docs-site/security.html +174 -0
- package/docs-site/team.html +142 -0
- package/docs-site/workflows.html +170 -0
- package/package.json +4 -2
- package/packages/piagent-core/capabilities/capability-core.js +58 -5
- package/packages/piagent-core/capabilities/capability-sources.js +73 -12
- package/packages/piagent-core/extensions/document-intake.ts +5 -5
- package/packages/piagent-core/extensions/piagent-guard.ts +432 -2
- package/packages/piagent-core/extensions/policy-core.js +1586 -98
- package/packages/piagent-core/mcp/mcp-approval-store.js +216 -0
- package/packages/piagent-core/mcp/mcp-auth-readiness.js +225 -0
- package/packages/piagent-core/mcp/mcp-command-actions.js +400 -0
- package/packages/piagent-core/mcp/mcp-config-layers.js +466 -0
- package/packages/piagent-core/mcp/mcp-import-kinds.js +240 -0
- package/packages/piagent-core/mcp/mcp-server-catalog.js +150 -0
- package/packages/piagent-core/mcp/mcp-server-entry.js +293 -0
- package/packages/piagent-core/mcp/mcp-session-view.js +236 -0
- package/packages/piagent-core/mcp/mcp-tool-naming.js +90 -0
- package/packages/piagent-core/package.json +7 -3
- package/packages/piagent-core/prompts/model-options.md +8 -8
- package/scripts/build-docs-site.mjs +421 -0
- package/scripts/configure-model-scope.sh +21 -5
- package/scripts/configure-subagents.sh +18 -5
- package/scripts/init-project.sh +15 -8
- package/scripts/install-global.sh +5 -5
- package/scripts/mcp-manage.mjs +656 -0
- package/scripts/pi-model-catalog.sh +11 -1
- package/scripts/pi-session-stats.sh +27 -1
- package/scripts/pi-usage-history.mjs +567 -0
- package/scripts/piagent-cli.mjs +1 -1
- package/scripts/preview-docs-site.mjs +88 -0
- package/scripts/quality-benchmark.sh +37 -9
- package/scripts/setup.sh +23 -15
- package/scripts/team-doctor.sh +18 -2
- package/scripts/uninstall-global.sh +10 -4
- package/scripts/verify-local.sh +65 -19
- package/scripts/verify-release-identity.mjs +37 -2
- package/scripts/configure-mcp.sh +0 -349
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import http from "node:http";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
// Serves docs-site/ locally the way Vercel serves it.
|
|
8
|
+
//
|
|
9
|
+
// The deployed site runs with `cleanUrls: true`, so every internal link points
|
|
10
|
+
// at /quickstart rather than /quickstart.html. A plain static file server
|
|
11
|
+
// answers 404 for all of them, which makes a local preview useless for
|
|
12
|
+
// checking navigation. This resolves the extensionless path the same way.
|
|
13
|
+
|
|
14
|
+
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
15
|
+
const siteRoot = path.join(repoRoot, "docs-site");
|
|
16
|
+
|
|
17
|
+
const CONTENT_TYPES = {
|
|
18
|
+
".css": "text/css; charset=utf-8",
|
|
19
|
+
".html": "text/html; charset=utf-8",
|
|
20
|
+
".ico": "image/x-icon",
|
|
21
|
+
".js": "text/javascript; charset=utf-8",
|
|
22
|
+
".json": "application/json; charset=utf-8",
|
|
23
|
+
".png": "image/png",
|
|
24
|
+
".svg": "image/svg+xml",
|
|
25
|
+
".txt": "text/plain; charset=utf-8",
|
|
26
|
+
".webmanifest": "application/manifest+json"
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function portFromArgs(argv) {
|
|
30
|
+
const flag = argv.indexOf("--port");
|
|
31
|
+
const raw = flag >= 0 ? argv[flag + 1] : process.env.PORT;
|
|
32
|
+
if (!raw) return 4321;
|
|
33
|
+
const port = Number.parseInt(raw, 10);
|
|
34
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
35
|
+
throw new Error(`invalid port: ${raw}`);
|
|
36
|
+
}
|
|
37
|
+
return port;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Resolve a request path to a file inside the site root, or undefined. */
|
|
41
|
+
function resolveFile(urlPath) {
|
|
42
|
+
const requested = urlPath === "/" ? "/index.html" : urlPath;
|
|
43
|
+
const candidate = path.resolve(siteRoot, `.${requested}`);
|
|
44
|
+
// A traversal such as /../../.ssh/id_rsa escapes the site root; refuse it
|
|
45
|
+
// rather than reading whatever it lands on.
|
|
46
|
+
if (candidate !== siteRoot && !candidate.startsWith(siteRoot + path.sep)) return undefined;
|
|
47
|
+
for (const file of [candidate, `${candidate}.html`, path.join(candidate, "index.html")]) {
|
|
48
|
+
if (fs.existsSync(file) && fs.statSync(file).isFile()) return file;
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const port = portFromArgs(process.argv.slice(2));
|
|
54
|
+
|
|
55
|
+
const server = http.createServer((req, res) => {
|
|
56
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
57
|
+
res.writeHead(405, { allow: "GET, HEAD" });
|
|
58
|
+
res.end();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
let urlPath;
|
|
62
|
+
try {
|
|
63
|
+
urlPath = decodeURIComponent(new URL(req.url, `http://localhost:${port}`).pathname);
|
|
64
|
+
} catch {
|
|
65
|
+
res.writeHead(400, { "content-type": "text/plain; charset=utf-8" });
|
|
66
|
+
res.end("bad request\n");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const file = resolveFile(urlPath);
|
|
70
|
+
if (!file) {
|
|
71
|
+
res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
|
|
72
|
+
res.end(`not found: ${urlPath}\n`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const body = fs.readFileSync(file);
|
|
76
|
+
res.writeHead(200, {
|
|
77
|
+
"content-type": CONTENT_TYPES[path.extname(file)] ?? "application/octet-stream",
|
|
78
|
+
"content-length": body.length,
|
|
79
|
+
"cache-control": "no-store"
|
|
80
|
+
});
|
|
81
|
+
res.end(req.method === "HEAD" ? undefined : body);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Loopback only: this serves local files and has no business being reachable
|
|
85
|
+
// from the network.
|
|
86
|
+
server.listen(port, "127.0.0.1", () => {
|
|
87
|
+
process.stdout.write(`docs-site preview on http://localhost:${port} (Ctrl+C to stop)\n`);
|
|
88
|
+
});
|
|
@@ -31,6 +31,14 @@ if [[ -z "$PROJECT_PATH" ]]; then
|
|
|
31
31
|
usage
|
|
32
32
|
exit 2
|
|
33
33
|
fi
|
|
34
|
+
# The project path is positional and comes first, so omitting it hands the first
|
|
35
|
+
# flag to `cd` and the run dies somewhere further down complaining about an
|
|
36
|
+
# option. Say what is actually wrong instead.
|
|
37
|
+
if [[ "$PROJECT_PATH" == -* ]]; then
|
|
38
|
+
echo "FAIL: the project path comes first, before any flag; received: $PROJECT_PATH" >&2
|
|
39
|
+
usage >&2
|
|
40
|
+
exit 2
|
|
41
|
+
fi
|
|
34
42
|
shift || true
|
|
35
43
|
|
|
36
44
|
PROJECT_PATH="$(cd "$PROJECT_PATH" && pwd)"
|
|
@@ -45,6 +53,17 @@ COST=""
|
|
|
45
53
|
DURATION=""
|
|
46
54
|
NOTES=""
|
|
47
55
|
|
|
56
|
+
# A flag left without a value otherwise consumes the next flag as its value, and
|
|
57
|
+
# the run continues on a record that says something nobody typed.
|
|
58
|
+
require_value() {
|
|
59
|
+
local option="$1"
|
|
60
|
+
local value="${2:-}"
|
|
61
|
+
if [[ -z "$value" || "$value" == --* ]]; then
|
|
62
|
+
echo "Missing value for $option" >&2
|
|
63
|
+
exit 2
|
|
64
|
+
fi
|
|
65
|
+
}
|
|
66
|
+
|
|
48
67
|
while [[ $# -gt 0 ]]; do
|
|
49
68
|
case "$1" in
|
|
50
69
|
--init)
|
|
@@ -56,39 +75,48 @@ while [[ $# -gt 0 ]]; do
|
|
|
56
75
|
shift
|
|
57
76
|
;;
|
|
58
77
|
--scenario)
|
|
59
|
-
|
|
78
|
+
require_value "$1" "${2:-}"
|
|
79
|
+
SCENARIO="$2"
|
|
60
80
|
shift 2
|
|
61
81
|
;;
|
|
62
82
|
--surface|--agent)
|
|
63
|
-
|
|
83
|
+
require_value "$1" "${2:-}"
|
|
84
|
+
SURFACE="$2"
|
|
64
85
|
shift 2
|
|
65
86
|
;;
|
|
66
87
|
--result)
|
|
67
|
-
|
|
88
|
+
require_value "$1" "${2:-}"
|
|
89
|
+
RESULT="$2"
|
|
68
90
|
shift 2
|
|
69
91
|
;;
|
|
70
92
|
--task-file)
|
|
71
|
-
|
|
93
|
+
require_value "$1" "${2:-}"
|
|
94
|
+
TASK_FILE="$2"
|
|
72
95
|
shift 2
|
|
73
96
|
;;
|
|
74
97
|
--verify)
|
|
75
|
-
|
|
98
|
+
require_value "$1" "${2:-}"
|
|
99
|
+
VERIFY_CMD="$2"
|
|
76
100
|
shift 2
|
|
77
101
|
;;
|
|
78
102
|
--tokens)
|
|
79
|
-
|
|
103
|
+
require_value "$1" "${2:-}"
|
|
104
|
+
TOKENS="$2"
|
|
80
105
|
shift 2
|
|
81
106
|
;;
|
|
82
107
|
--cost)
|
|
83
|
-
|
|
108
|
+
require_value "$1" "${2:-}"
|
|
109
|
+
COST="$2"
|
|
84
110
|
shift 2
|
|
85
111
|
;;
|
|
86
112
|
--duration)
|
|
87
|
-
|
|
113
|
+
require_value "$1" "${2:-}"
|
|
114
|
+
DURATION="$2"
|
|
88
115
|
shift 2
|
|
89
116
|
;;
|
|
90
117
|
--notes)
|
|
91
|
-
|
|
118
|
+
require_value "$1" "${2:-}"
|
|
119
|
+
NOTES="$2"
|
|
92
120
|
shift 2
|
|
93
121
|
;;
|
|
94
122
|
-h|--help)
|
package/scripts/setup.sh
CHANGED
|
@@ -28,11 +28,11 @@ Options:
|
|
|
28
28
|
Install/skip pi-subagents during global install (default: install)
|
|
29
29
|
--subagents-preset <minimal|safe|async|parallel>
|
|
30
30
|
Configure pi-subagents runtime baseline (default: safe)
|
|
31
|
-
--subagents-model-scope <none|piagent|codex|claude>
|
|
31
|
+
--subagents-model-scope <none|piagent|codex|claude> model family for subagents
|
|
32
32
|
Optional subagent model allowlist (default: none)
|
|
33
33
|
--with-web-access Install pi-web-access for builtin `researcher` subagent (default: skip)
|
|
34
34
|
--with-herdr / --no-herdr Install/skip Herdr Pi integration if herdr exists (default: install)
|
|
35
|
-
--model-scope <full|codex|claude>
|
|
35
|
+
--model-scope <full|codex|claude> model families to enable
|
|
36
36
|
Configure Pi enabledModels for selector/cycling (default: full)
|
|
37
37
|
--default-model <provider/model[:thinking]>
|
|
38
38
|
Configure default provider/model/thinking (default: openai-codex/gpt-5.5:xhigh)
|
|
@@ -85,21 +85,24 @@ SKIP_AGENTS=false
|
|
|
85
85
|
SKIP_REVIEW=false
|
|
86
86
|
DRY_RUN=false
|
|
87
87
|
|
|
88
|
+
require_value() {
|
|
89
|
+
local option="$1"
|
|
90
|
+
local value="${2:-}"
|
|
91
|
+
if [[ -z "$value" || "$value" == --* ]]; then
|
|
92
|
+
echo "Missing value for $option" >&2
|
|
93
|
+
exit 2
|
|
94
|
+
fi
|
|
95
|
+
}
|
|
96
|
+
|
|
88
97
|
while [[ $# -gt 0 ]]; do
|
|
89
98
|
case "$1" in
|
|
90
99
|
--profile)
|
|
91
|
-
|
|
92
|
-
echo "Missing value for --profile" >&2
|
|
93
|
-
exit 2
|
|
94
|
-
fi
|
|
100
|
+
require_value "$1" "${2:-}"
|
|
95
101
|
PROFILE_INPUT="$2"
|
|
96
102
|
shift 2
|
|
97
103
|
;;
|
|
98
104
|
--package-source)
|
|
99
|
-
|
|
100
|
-
echo "Missing value for --package-source" >&2
|
|
101
|
-
exit 2
|
|
102
|
-
fi
|
|
105
|
+
require_value "$1" "${2:-}"
|
|
103
106
|
PACKAGE_SOURCE="$2"
|
|
104
107
|
shift 2
|
|
105
108
|
;;
|
|
@@ -124,7 +127,8 @@ while [[ $# -gt 0 ]]; do
|
|
|
124
127
|
shift
|
|
125
128
|
;;
|
|
126
129
|
--mcp-preset)
|
|
127
|
-
|
|
130
|
+
require_value "$1" "${2:-}"
|
|
131
|
+
MCP_PRESET="$2"
|
|
128
132
|
MCP_PRESET_EXPLICIT=true
|
|
129
133
|
shift 2
|
|
130
134
|
;;
|
|
@@ -137,11 +141,13 @@ while [[ $# -gt 0 ]]; do
|
|
|
137
141
|
shift
|
|
138
142
|
;;
|
|
139
143
|
--subagents-preset)
|
|
140
|
-
|
|
144
|
+
require_value "$1" "${2:-}"
|
|
145
|
+
SUBAGENTS_PRESET="$2"
|
|
141
146
|
shift 2
|
|
142
147
|
;;
|
|
143
148
|
--subagents-model-scope)
|
|
144
|
-
|
|
149
|
+
require_value "$1" "${2:-}"
|
|
150
|
+
SUBAGENTS_MODEL_SCOPE="$2"
|
|
145
151
|
shift 2
|
|
146
152
|
;;
|
|
147
153
|
--with-web-access)
|
|
@@ -157,11 +163,13 @@ while [[ $# -gt 0 ]]; do
|
|
|
157
163
|
shift
|
|
158
164
|
;;
|
|
159
165
|
--model-scope)
|
|
160
|
-
|
|
166
|
+
require_value "$1" "${2:-}"
|
|
167
|
+
MODEL_SCOPE_PRESET="$2"
|
|
161
168
|
shift 2
|
|
162
169
|
;;
|
|
163
170
|
--default-model)
|
|
164
|
-
|
|
171
|
+
require_value "$1" "${2:-}"
|
|
172
|
+
DEFAULT_MODEL="$2"
|
|
165
173
|
shift 2
|
|
166
174
|
;;
|
|
167
175
|
--no-model-scope)
|
package/scripts/team-doctor.sh
CHANGED
|
@@ -137,7 +137,7 @@ function runtimeMatrixStatus() {
|
|
|
137
137
|
surface: `${platform}/${arch}`,
|
|
138
138
|
status: "outside-release-matrix",
|
|
139
139
|
teamRolloutReady: false,
|
|
140
|
-
note: "This runtime is outside the documented v1.
|
|
140
|
+
note: "This runtime is outside the documented v1.2.0 support matrix."
|
|
141
141
|
};
|
|
142
142
|
}
|
|
143
143
|
|
|
@@ -169,7 +169,7 @@ for (const rel of [
|
|
|
169
169
|
"templates/project/.pi/memory/memory_summary.md",
|
|
170
170
|
"templates/project/.pi/memory/MEMORY.md",
|
|
171
171
|
"scripts/setup.sh",
|
|
172
|
-
"scripts/
|
|
172
|
+
"scripts/mcp-manage.mjs",
|
|
173
173
|
"scripts/configure-subagents.sh"
|
|
174
174
|
]) {
|
|
175
175
|
if (!exists(rel)) errors.push(`missing platform file: ${rel}`);
|
|
@@ -247,6 +247,22 @@ if (piHasMcpAdapter && totalMcpServers === 0) {
|
|
|
247
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
248
|
}
|
|
249
249
|
|
|
250
|
+
// Counting servers says nothing about whether any of them can answer. A server
|
|
251
|
+
// whose token is not exported, or which this repository defines and nobody
|
|
252
|
+
// approved, is configured and unusable, and finding that out when a call fails
|
|
253
|
+
// is the thing this check exists to prevent.
|
|
254
|
+
try {
|
|
255
|
+
const { collectServers } = await import(pathToFileURL(path.join(platformRoot, "packages", "piagent-core", "mcp", "mcp-config-layers.js")).href);
|
|
256
|
+
const { evaluateServerReadiness, BLOCKING_STATES } = await import(pathToFileURL(path.join(platformRoot, "packages", "piagent-core", "mcp", "mcp-auth-readiness.js")).href);
|
|
257
|
+
for (const server of collectServers({ projectPath })) {
|
|
258
|
+
const readiness = evaluateServerReadiness(server, { projectPath });
|
|
259
|
+
if (!BLOCKING_STATES.has(readiness.state)) continue;
|
|
260
|
+
warnings.push(`MCP server ${server.name} (${server.scope}) is configured but not usable: ${readiness.detail}${readiness.remedy ? `; ${readiness.remedy}` : ""}`);
|
|
261
|
+
}
|
|
262
|
+
} catch (error) {
|
|
263
|
+
warnings.push(`MCP readiness check could not run: ${error instanceof Error ? error.message : String(error)}`);
|
|
264
|
+
}
|
|
265
|
+
|
|
250
266
|
const subagentConfigPath = path.join(process.env.PI_CODING_AGENT_DIR || path.join(process.env.HOME || "", ".pi", "agent"), "extensions", "subagent", "config.json");
|
|
251
267
|
const subagentConfig = readJsonIfPresent(subagentConfigPath);
|
|
252
268
|
if (piHasSubagents && !subagentConfig) {
|
|
@@ -39,16 +39,22 @@ todos, and project memory. Those are operator data, not platform state.
|
|
|
39
39
|
USAGE
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
require_value() {
|
|
43
|
+
local option="$1"
|
|
44
|
+
local value="${2:-}"
|
|
45
|
+
if [[ -z "$value" || "$value" == --* ]]; then
|
|
46
|
+
echo "Missing value for $option" >&2
|
|
47
|
+
exit 2
|
|
48
|
+
fi
|
|
49
|
+
}
|
|
50
|
+
|
|
42
51
|
while [[ $# -gt 0 ]]; do
|
|
43
52
|
case "$1" in
|
|
44
53
|
--apply) APPLY=true; shift ;;
|
|
45
54
|
--with-addons) WITH_ADDONS=true; shift ;;
|
|
46
55
|
--with-host) WITH_HOST=true; shift ;;
|
|
47
56
|
--project)
|
|
48
|
-
|
|
49
|
-
echo "Missing value for --project" >&2
|
|
50
|
-
exit 2
|
|
51
|
-
fi
|
|
57
|
+
require_value "$1" "${2:-}"
|
|
52
58
|
PROJECT_PATH="$2"
|
|
53
59
|
shift 2
|
|
54
60
|
;;
|
package/scripts/verify-local.sh
CHANGED
|
@@ -126,9 +126,16 @@ required_files=(
|
|
|
126
126
|
"$ROOT/docs/capability-packs.md"
|
|
127
127
|
"$ROOT/docs/runtime-policy-design.md"
|
|
128
128
|
"$ROOT/docs-site/index.html"
|
|
129
|
+
"$ROOT/docs-site/mcp.html"
|
|
129
130
|
"$ROOT/docs-site/favicon.svg"
|
|
130
131
|
"$ROOT/docs-site/assets/piagent-logo.svg"
|
|
132
|
+
"$ROOT/docs-site/assets/docs.css"
|
|
133
|
+
"$ROOT/docs-site/assets/docs.js"
|
|
134
|
+
"$ROOT/docs-site/content/index.html"
|
|
135
|
+
"$ROOT/docs-site/content/mcp.html"
|
|
131
136
|
"$ROOT/docs-site/vercel.json"
|
|
137
|
+
"$ROOT/scripts/build-docs-site.mjs"
|
|
138
|
+
"$ROOT/scripts/preview-docs-site.mjs"
|
|
132
139
|
"$ROOT/schemas/project-profile.schema.json"
|
|
133
140
|
"$ROOT/schemas/task-contract.schema.json"
|
|
134
141
|
"$ROOT/schemas/capability-pack.schema.json"
|
|
@@ -157,10 +164,11 @@ required_files=(
|
|
|
157
164
|
"$ROOT/scripts/quality-benchmark.sh"
|
|
158
165
|
"$ROOT/scripts/runtime-policy-smoke.sh"
|
|
159
166
|
"$ROOT/scripts/pi-session-stats.sh"
|
|
167
|
+
"$ROOT/scripts/pi-usage-history.mjs"
|
|
160
168
|
"$ROOT/scripts/pi-auto.sh"
|
|
161
169
|
"$ROOT/scripts/pi-model-catalog.sh"
|
|
162
170
|
"$ROOT/scripts/configure-model-scope.sh"
|
|
163
|
-
"$ROOT/scripts/
|
|
171
|
+
"$ROOT/scripts/mcp-manage.mjs"
|
|
164
172
|
"$ROOT/scripts/configure-subagents.sh"
|
|
165
173
|
"$ROOT/scripts/capability-catalog.mjs"
|
|
166
174
|
"$ROOT/scripts/migrate-project-state.mjs"
|
|
@@ -182,6 +190,9 @@ required_files=(
|
|
|
182
190
|
"$ROOT/tests/document-intake.test.mjs"
|
|
183
191
|
"$ROOT/tests/runtime-advisories.test.mjs"
|
|
184
192
|
"$ROOT/scripts/check-runtime-advisories.mjs"
|
|
193
|
+
"$ROOT/packages/piagent-core/mcp/mcp-session-view.js"
|
|
194
|
+
"$ROOT/packages/piagent-core/mcp/mcp-command-actions.js"
|
|
195
|
+
"$ROOT/tests/mcp-session-command.test.mjs"
|
|
185
196
|
"$ROOT/tests/migrate-project-state.test.mjs"
|
|
186
197
|
"$ROOT/tests/uninstall-global.test.mjs"
|
|
187
198
|
"$ROOT/tests/import-agent-instructions.test.mjs"
|
|
@@ -413,33 +424,33 @@ require_documented "gpt-5.6" "$ROOT/README.md" "$ROOT/docs/model-options.md" "$R
|
|
|
413
424
|
require_documented "claude-fable-5" "$ROOT/README.md" "$ROOT/docs/model-options.md" "$ROOT/packages/piagent-core/prompts/model-options.md"
|
|
414
425
|
require_documented "piagent-models" "$ROOT/README.md" "$ROOT/docs/model-options.md"
|
|
415
426
|
require_documented "enabledModels" "$ROOT/templates/global/settings.json" "$ROOT/docs/model-options.md" "$ROOT/scripts/configure-model-scope.sh"
|
|
416
|
-
require_documented "piagent-mcp" "$ROOT/README.md" "$ROOT/docs/mcp-and-tools.md" "$ROOT/scripts/
|
|
427
|
+
require_documented "piagent-mcp" "$ROOT/README.md" "$ROOT/docs/mcp-and-tools.md" "$ROOT/scripts/mcp-manage.mjs"
|
|
417
428
|
require_documented "pi-mcp-adapter" "$ROOT/README.md" "$ROOT/docs/mcp-and-tools.md" "$ROOT/scripts/install-global.sh"
|
|
418
429
|
require_documented "piagent-subagents" "$ROOT/README.md" "$ROOT/docs/subagents-and-multiagent.md" "$ROOT/scripts/configure-subagents.sh"
|
|
419
430
|
require_documented "subagents-fleet" "$ROOT/docs/command-reference-vietnamese.md" "$ROOT/docs/subagents-and-multiagent.md" "$ROOT/README.md"
|
|
420
431
|
require_documented "health check" "$ROOT/docs/command-reference-vietnamese.md" "$ROOT/docs/subagents-and-multiagent.md" "$ROOT/README.md"
|
|
421
432
|
require_documented "pi-subagents" "$ROOT/README.md" "$ROOT/docs/subagents-and-multiagent.md" "$ROOT/scripts/install-global.sh" "$ROOT/scripts/setup.sh"
|
|
422
433
|
require_documented "piagent-scout" "$ROOT/README.md" "$ROOT/docs/subagents-and-multiagent.md" "$ROOT/packages/piagent-core/subagents"
|
|
423
|
-
grep -F "@upstash/context7-mcp@3.2.4" "$ROOT/
|
|
434
|
+
grep -F "@upstash/context7-mcp@3.2.4" "$ROOT/packages/piagent-core/mcp/mcp-server-catalog.js" >/dev/null
|
|
424
435
|
grep -F "@upstash/context7-mcp@3.2.4" "$ROOT/docs/mcp-and-tools.md" >/dev/null
|
|
425
|
-
grep -F "chrome-devtools-mcp@1.6.0" "$ROOT/
|
|
436
|
+
grep -F "chrome-devtools-mcp@1.6.0" "$ROOT/packages/piagent-core/mcp/mcp-server-catalog.js" >/dev/null
|
|
426
437
|
grep -F "chrome-devtools-mcp@1.6.0" "$ROOT/docs/mcp-and-tools.md" >/dev/null
|
|
427
|
-
grep -F "@playwright/mcp@0.0.78" "$ROOT/
|
|
438
|
+
grep -F "@playwright/mcp@0.0.78" "$ROOT/packages/piagent-core/mcp/mcp-server-catalog.js" >/dev/null
|
|
428
439
|
grep -F "@playwright/mcp@0.0.78" "$ROOT/docs/mcp-and-tools.md" >/dev/null
|
|
429
|
-
require_documented "https://mcp.figma.com/mcp" "$ROOT/
|
|
430
|
-
grep -F "ghcr.io/github/github-mcp-server@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3" "$ROOT/
|
|
440
|
+
require_documented "https://mcp.figma.com/mcp" "$ROOT/packages/piagent-core/mcp/mcp-server-catalog.js" "$ROOT/docs/mcp-and-tools.md"
|
|
441
|
+
grep -F "ghcr.io/github/github-mcp-server@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3" "$ROOT/packages/piagent-core/mcp/mcp-server-catalog.js" >/dev/null
|
|
431
442
|
grep -F "ghcr.io/github/github-mcp-server@sha256:2b0c48b070f61e9d3969269ead600f62d00fb237b60ac849ef3d166ee7de9ad3" "$ROOT/docs/mcp-and-tools.md" >/dev/null
|
|
432
|
-
grep -F '"GITHUB_READ_ONLY=1"' "$ROOT/
|
|
443
|
+
grep -F '"GITHUB_READ_ONLY=1"' "$ROOT/packages/piagent-core/mcp/mcp-server-catalog.js" >/dev/null
|
|
433
444
|
grep -F '"GITHUB_READ_ONLY=1"' "$ROOT/docs/mcp-and-tools.md" >/dev/null
|
|
434
|
-
grep -F '"GITHUB_LOCKDOWN_MODE=1"' "$ROOT/
|
|
445
|
+
grep -F '"GITHUB_LOCKDOWN_MODE=1"' "$ROOT/packages/piagent-core/mcp/mcp-server-catalog.js" >/dev/null
|
|
435
446
|
grep -F '"GITHUB_LOCKDOWN_MODE=1"' "$ROOT/docs/mcp-and-tools.md" >/dev/null
|
|
436
|
-
grep -F 'CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS: "1"' "$ROOT/
|
|
447
|
+
grep -F 'CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS: "1"' "$ROOT/packages/piagent-core/mcp/mcp-server-catalog.js" >/dev/null
|
|
437
448
|
grep -F '"CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS": "1"' "$ROOT/docs/mcp-and-tools.md" >/dev/null
|
|
438
|
-
grep -F 'CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS: "1"' "$ROOT/
|
|
449
|
+
grep -F 'CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS: "1"' "$ROOT/packages/piagent-core/mcp/mcp-server-catalog.js" >/dev/null
|
|
439
450
|
grep -F '"CHROME_DEVTOOLS_MCP_NO_UPDATE_CHECKS": "1"' "$ROOT/docs/mcp-and-tools.md" >/dev/null
|
|
440
|
-
grep -F '
|
|
441
|
-
grep -F 'piagent-mcp --preset core --scope global --replace' "$ROOT/scripts/
|
|
442
|
-
if grep -E '@latest|"@upstash/context7-mcp"|"chrome-devtools-mcp"|"@playwright/mcp"|"ghcr\.io/github/github-mcp-server"' "$ROOT/
|
|
451
|
+
grep -F 'mcp-manage.mjs" --scope global --preset "$MCP_PRESET" --replace' "$ROOT/scripts/install-global.sh" >/dev/null
|
|
452
|
+
grep -F 'piagent-mcp --preset core --scope global --replace' "$ROOT/scripts/mcp-manage.mjs" >/dev/null
|
|
453
|
+
if grep -E '@latest|"@upstash/context7-mcp"|"chrome-devtools-mcp"|"@playwright/mcp"|"ghcr\.io/github/github-mcp-server"' "$ROOT/packages/piagent-core/mcp/mcp-server-catalog.js" >/dev/null; then
|
|
443
454
|
echo "MCP production presets contain a mutable dependency source"
|
|
444
455
|
exit 1
|
|
445
456
|
fi
|
|
@@ -459,6 +470,23 @@ require_documented "piagent-capabilities" "$ROOT/README.md" "$ROOT/docs/capabili
|
|
|
459
470
|
require_documented ".pi-subagents/" "$ROOT/.gitignore" "$ROOT/docs/subagents-and-multiagent.md" "$ROOT/docs/distribution-standard.md" "$ROOT/scripts/init-project.sh"
|
|
460
471
|
test -s "$ROOT/tests/policy-core.test.mjs"
|
|
461
472
|
|
|
473
|
+
# Every gate below this line is a grep, and grep skips a file it decides is
|
|
474
|
+
# binary. One control byte written into a string literal — a NUL used as a key
|
|
475
|
+
# separator is the easy way to do it by accident — takes the whole file out of
|
|
476
|
+
# every one of them, and out of the diff a reviewer reads on a pull request. The
|
|
477
|
+
# file still runs, so nothing else notices. Escapes carry the same bytes and keep
|
|
478
|
+
# the file readable.
|
|
479
|
+
non_text_sources="$(
|
|
480
|
+
cd "$ROOT" && git ls-files -z -- '*.js' '*.mjs' '*.ts' '*.json' '*.md' '*.sh' '*.html' '*.css' \
|
|
481
|
+
| LC_ALL=C xargs -0 -I{} sh -c 'grep -qI "" "{}" 2>/dev/null || echo "{}"'
|
|
482
|
+
)"
|
|
483
|
+
if [[ -n "$non_text_sources" ]]; then
|
|
484
|
+
echo "Source files contain control bytes, so grep-based gates and diffs skip them:"
|
|
485
|
+
echo "$non_text_sources" | sed 's/^/ /'
|
|
486
|
+
echo "Write the byte as an escape (\\u0000, \\x03) instead of embedding it."
|
|
487
|
+
exit 1
|
|
488
|
+
fi
|
|
489
|
+
|
|
462
490
|
public_wording_pattern="$(
|
|
463
491
|
node --input-type=module <<'NODE'
|
|
464
492
|
const terms = [
|
|
@@ -471,6 +499,9 @@ const terms = [
|
|
|
471
499
|
["mit", "suhiko"].join(""),
|
|
472
500
|
["Cod", "ex CLI"].join(""),
|
|
473
501
|
["Claude", " CLI"].join(""),
|
|
502
|
+
["Claude", " Code"].join(""),
|
|
503
|
+
["claude", " mcp"].join(""),
|
|
504
|
+
["codex", " mcp"].join(""),
|
|
474
505
|
["Cod", "ex-inspired"].join(""),
|
|
475
506
|
["Cod", "ex-grade"].join(""),
|
|
476
507
|
["Pi vs ", "Codex"].join(""),
|
|
@@ -485,12 +516,21 @@ console.log(terms.map(escapeRegex).join("|"));
|
|
|
485
516
|
NODE
|
|
486
517
|
)"
|
|
487
518
|
|
|
519
|
+
# Every surface an outsider reads: the published docs, the site, and the strings
|
|
520
|
+
# the runtime prints back at an operator. The site is checked at its source,
|
|
521
|
+
# since `build-docs-site.mjs --check` separately proves the committed pages match
|
|
522
|
+
# it; the builder itself is listed too, because navigation labels and page titles
|
|
523
|
+
# live there rather than in a content fragment.
|
|
488
524
|
if grep -R -E -i \
|
|
489
525
|
"$public_wording_pattern" \
|
|
490
526
|
"$ROOT/README.md" \
|
|
491
527
|
"$ROOT/docs" \
|
|
528
|
+
"$ROOT/docs-site/content" \
|
|
529
|
+
"$ROOT/scripts/build-docs-site.mjs" \
|
|
492
530
|
"$ROOT/packages/piagent-core/README.md" \
|
|
493
531
|
"$ROOT/packages/piagent-core/prompts" \
|
|
532
|
+
"$ROOT/packages/piagent-core/mcp" \
|
|
533
|
+
"$ROOT/packages/piagent-core/extensions" \
|
|
494
534
|
"$ROOT/templates/project/AGENTS.md" >/dev/null; then
|
|
495
535
|
echo "Public docs contain non-neutral platform wording"
|
|
496
536
|
exit 1
|
|
@@ -505,10 +545,13 @@ node --check "$ROOT/packages/piagent-core/capabilities/capability-core.js" >/dev
|
|
|
505
545
|
node --check "$ROOT/packages/piagent-core/capabilities/capability-sources.js" >/dev/null
|
|
506
546
|
node --check "$ROOT/scripts/capability-catalog.mjs" >/dev/null
|
|
507
547
|
node --check "$ROOT/scripts/piagent-cli.mjs" >/dev/null
|
|
548
|
+
node --check "$ROOT/scripts/pi-usage-history.mjs" >/dev/null
|
|
508
549
|
node --check "$ROOT/scripts/migrate-project-state.mjs" >/dev/null
|
|
509
550
|
node --check "$ROOT/scripts/import-agent-instructions.mjs" >/dev/null
|
|
510
551
|
node --check "$ROOT/scripts/check-runtime-advisories.mjs" >/dev/null
|
|
511
552
|
node --check "$ROOT/scripts/check-published-site.mjs" >/dev/null
|
|
553
|
+
node --check "$ROOT/scripts/build-docs-site.mjs" >/dev/null
|
|
554
|
+
node --check "$ROOT/scripts/preview-docs-site.mjs" >/dev/null
|
|
512
555
|
run_quietly "npm test" npm test
|
|
513
556
|
if [[ -x "$ROOT/node_modules/.bin/tsc" ]]; then
|
|
514
557
|
run_quietly "npm run typecheck" npm run typecheck
|
|
@@ -519,7 +562,9 @@ bash -n "$ROOT/scripts/pi-session-stats.sh"
|
|
|
519
562
|
bash -n "$ROOT/scripts/pi-auto.sh"
|
|
520
563
|
bash -n "$ROOT/scripts/pi-model-catalog.sh"
|
|
521
564
|
bash -n "$ROOT/scripts/configure-model-scope.sh"
|
|
522
|
-
|
|
565
|
+
node --check "$ROOT/scripts/mcp-manage.mjs"
|
|
566
|
+
node --check "$ROOT/packages/piagent-core/mcp/mcp-session-view.js"
|
|
567
|
+
node --check "$ROOT/packages/piagent-core/mcp/mcp-command-actions.js"
|
|
523
568
|
bash -n "$ROOT/scripts/configure-subagents.sh"
|
|
524
569
|
bash -n "$ROOT/scripts/install-global.sh"
|
|
525
570
|
bash -n "$ROOT/scripts/init-project.sh"
|
|
@@ -531,8 +576,8 @@ else
|
|
|
531
576
|
bash "$ROOT/scripts/pi-model-catalog.sh" --json >/dev/null
|
|
532
577
|
fi
|
|
533
578
|
bash "$ROOT/scripts/configure-model-scope.sh" --dry-run --preset full --default-model openai-codex/gpt-5.5:xhigh >/dev/null
|
|
534
|
-
|
|
535
|
-
|
|
579
|
+
node "$ROOT/scripts/mcp-manage.mjs" --list >/dev/null
|
|
580
|
+
node "$ROOT/scripts/mcp-manage.mjs" --dry-run --preset popular --scope project --project "$ROOT" >/dev/null
|
|
536
581
|
node --input-type=module - "$ROOT" <<'NODE'
|
|
537
582
|
import assert from "node:assert/strict";
|
|
538
583
|
import { execFileSync } from "node:child_process";
|
|
@@ -553,8 +598,8 @@ try {
|
|
|
553
598
|
internal: { url: "https://mcp.example.invalid/api" }
|
|
554
599
|
}
|
|
555
600
|
}, null, 2)}\n`);
|
|
556
|
-
execFileSync(
|
|
557
|
-
path.join(root, "scripts", "
|
|
601
|
+
execFileSync(process.execPath, [
|
|
602
|
+
path.join(root, "scripts", "mcp-manage.mjs"),
|
|
558
603
|
"--config", configPath,
|
|
559
604
|
"--preset", "popular",
|
|
560
605
|
"--replace"
|
|
@@ -577,6 +622,7 @@ bash "$ROOT/scripts/configure-subagents.sh" --list >/dev/null
|
|
|
577
622
|
bash "$ROOT/scripts/configure-subagents.sh" --dry-run --preset safe >/dev/null
|
|
578
623
|
bash "$ROOT/scripts/runtime-policy-smoke.sh" >/dev/null
|
|
579
624
|
|
|
625
|
+
node "$ROOT/scripts/build-docs-site.mjs" --check >/dev/null
|
|
580
626
|
node "$ROOT/scripts/capability-catalog.mjs" catalog --check >/dev/null
|
|
581
627
|
node "$ROOT/scripts/capability-catalog.mjs" doctor --profile "$ROOT/.pi/piagent-profile.json" --lock "$ROOT/.pi/piagent-profile.lock.json" >/dev/null
|
|
582
628
|
node "$ROOT/scripts/capability-catalog.mjs" doctor --profile "$ROOT/adapters/generic/profile.json" >/dev/null
|
|
@@ -71,8 +71,43 @@ if (capabilityLock.core?.packageVersion !== version) fail("capability lock packa
|
|
|
71
71
|
const changelog = fs.readFileSync(path.join(root, "CHANGELOG.md"), "utf8");
|
|
72
72
|
if (!changelog.includes(`## ${expectedTag} -`)) fail(`CHANGELOG.md has no ${expectedTag} release section`);
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
// The badge is generated onto every page, so a stale build shows up as one page
|
|
75
|
+
// still carrying the previous version rather than as a single missed edit.
|
|
76
|
+
const docsPages = fs.readdirSync(path.join(root, "docs-site")).filter((name) => name.endsWith(".html"));
|
|
77
|
+
if (docsPages.length === 0) fail("docs-site has no generated pages");
|
|
78
|
+
for (const page of docsPages) {
|
|
79
|
+
const html = fs.readFileSync(path.join(root, "docs-site", page), "utf8");
|
|
80
|
+
if (!html.includes(`${expectedTag} docs`)) fail(`docs-site/${page} version badge does not identify ${expectedTag}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// A version written into an install command does not come from the build, so it
|
|
84
|
+
// goes stale silently and keeps telling readers to install the previous release.
|
|
85
|
+
// The badge check above cannot see these: they are sentences, not generated
|
|
86
|
+
// markup.
|
|
87
|
+
//
|
|
88
|
+
// Only the command shapes are matched, not every version-looking string. Prose
|
|
89
|
+
// naming an old release is usually saying something true about history — when a
|
|
90
|
+
// behaviour arrived, which release a mistake shipped in — and rewriting those to
|
|
91
|
+
// the current number would make them false. Files that exist to list past
|
|
92
|
+
// versions are exempt outright.
|
|
93
|
+
const VERSION_HISTORY_FILES = new Set(["CHANGELOG.md", "docs/publishing-for-teams.md"]);
|
|
94
|
+
const RELEASE_TAG_MENTION = /(?:@piagent\/platform@|Vt-mmm\/piagent@v?|--version )(\d+\.\d+\.\d+)/g;
|
|
95
|
+
|
|
96
|
+
function proseFiles() {
|
|
97
|
+
const files = fs.readdirSync(path.join(root, "docs")).filter((name) => name.endsWith(".md")).map((name) => `docs/${name}`);
|
|
98
|
+
files.push("README.md");
|
|
99
|
+
for (const name of fs.readdirSync(path.join(root, "docs-site", "content"))) {
|
|
100
|
+
if (name.endsWith(".html")) files.push(`docs-site/content/${name}`);
|
|
101
|
+
}
|
|
102
|
+
return files.filter((file) => !VERSION_HISTORY_FILES.has(file));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const file of proseFiles()) {
|
|
106
|
+
const text = fs.readFileSync(path.join(root, file), "utf8");
|
|
107
|
+
for (const [mention, named] of text.matchAll(RELEASE_TAG_MENTION)) {
|
|
108
|
+
if (named !== version) fail(`${file} tells the reader to install ${named} (${mention.trim()}), but this release is ${version}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
76
111
|
|
|
77
112
|
if (options.tag) {
|
|
78
113
|
if (options.tag !== expectedTag) fail(`release tag ${options.tag} does not match package version ${expectedTag}`);
|