@piagent/platform 1.2.8 → 1.2.10

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/README.md +4 -4
  3. package/catalog/capabilities.json +2 -2
  4. package/docs/command-reference-vietnamese.md +7 -4
  5. package/docs/context-engine.md +30 -1
  6. package/docs/distribution-standard.md +7 -7
  7. package/docs/mcp-and-tools.md +7 -0
  8. package/docs/operator-manual-vietnamese.md +27 -11
  9. package/docs/publishing-for-teams.md +2 -0
  10. package/docs/release-install-policy.md +6 -6
  11. package/docs/security-threat-model.md +7 -1
  12. package/docs/subagents-and-multiagent.md +1 -1
  13. package/docs/team-onboarding.md +2 -2
  14. package/docs-site/architecture.html +1 -1
  15. package/docs-site/commands.html +1 -1
  16. package/docs-site/content/context-engine.html +8 -1
  17. package/docs-site/content/security.html +1 -0
  18. package/docs-site/context-engine.html +9 -2
  19. package/docs-site/index.html +1 -1
  20. package/docs-site/mcp-approval.html +1 -1
  21. package/docs-site/mcp-auth.html +1 -1
  22. package/docs-site/mcp-servers.html +1 -1
  23. package/docs-site/mcp.html +1 -1
  24. package/docs-site/permissions.html +1 -1
  25. package/docs-site/profiles.html +1 -1
  26. package/docs-site/quickstart.html +1 -1
  27. package/docs-site/runtime.html +1 -1
  28. package/docs-site/scope.html +1 -1
  29. package/docs-site/security.html +2 -1
  30. package/docs-site/team.html +1 -1
  31. package/docs-site/workflows.html +1 -1
  32. package/package.json +1 -1
  33. package/packages/piagent-core/README.md +6 -1
  34. package/packages/piagent-core/capabilities/capability-core.js +3 -0
  35. package/packages/piagent-core/extensions/context-engine.js +263 -120
  36. package/packages/piagent-core/extensions/context-index-policy.js +136 -0
  37. package/packages/piagent-core/extensions/piagent-guard.ts +258 -101
  38. package/packages/piagent-core/mcp/mcp-config-layers.js +52 -6
  39. package/packages/piagent-core/mcp/mcp-tool-naming.js +12 -1
  40. package/packages/piagent-core/package.json +1 -1
  41. package/packages/piagent-core/subagents/piagent-reviewer.md +3 -3
  42. package/scripts/context-engine.mjs +22 -20
  43. package/scripts/update-global.mjs +1 -1
  44. package/scripts/verify-local.sh +2 -0
@@ -8,7 +8,7 @@ import {
8
8
  importedServers,
9
9
  unreadableImportTargets
10
10
  } from "./mcp-import-kinds.js";
11
- import { erasesToolOrigin } from "./mcp-tool-naming.js";
11
+ import { directToolsEnabled, erasesToolOrigin } from "./mcp-tool-naming.js";
12
12
 
13
13
  // Four files can define MCP servers, and which one a change belongs in is the
14
14
  // first thing anyone gets wrong. The scope names are the vocabulary the CLI
@@ -253,13 +253,29 @@ export function mergedServerEntry(options) {
253
253
  } catch {
254
254
  continue;
255
255
  }
256
- const entry = config.mcpServers[options.name];
257
- if (!isRecord(entry)) continue;
258
- merged = { ...merged, ...entry };
256
+ let layerEntry = {};
257
+ let layerSeen = false;
258
+ // The adapter expands imports inside each layer before applying that
259
+ // layer's direct server map. First imported definition wins; the direct
260
+ // entry then overrides it field by field.
261
+ for (const kind of config.imports) {
262
+ const imported = importedServers(kind, options).find((server) => server.name === options.name);
263
+ if (!imported) continue;
264
+ layerEntry = { ...imported.entry };
265
+ layerSeen = true;
266
+ break;
267
+ }
268
+ const directEntry = config.mcpServers[options.name];
269
+ if (isRecord(directEntry)) {
270
+ layerEntry = { ...layerEntry, ...directEntry };
271
+ layerSeen = true;
272
+ }
273
+ if (!layerSeen) continue;
274
+ merged = { ...merged, ...layerEntry };
259
275
  seen = true;
260
276
  }
261
- // A server reached through `imports` is in no layer's `mcpServers`, so the
262
- // entry the caller already has is the whole definition.
277
+ // A server supplied directly by a caller outside the configured layers keeps
278
+ // its own definition as the fallback.
263
279
  return seen ? merged : { ...(options.entry ?? {}) };
264
280
  }
265
281
 
@@ -430,6 +446,36 @@ export function unverifiableMcpConfig(options = {}) {
430
446
  });
431
447
  }
432
448
 
449
+ if (merged.settings.toolPrefix === "none" && !erasesToolOrigin(merged.settings)) {
450
+ const prefixScope = merged.contributors.toolPrefix;
451
+ const prefixFile = prefixScope
452
+ ? configPathForScope(prefixScope, options)
453
+ : configPathForScope("global", options);
454
+ const affected = new Map();
455
+ for (const server of collectServers(options)) {
456
+ if (!server.requiresApproval || affected.has(server.name)) continue;
457
+ const effective = mergedServerEntry({
458
+ ...options,
459
+ name: server.name,
460
+ entry: server.entry
461
+ });
462
+ if (!directToolsEnabled(effective.directTools)) continue;
463
+ affected.set(server.name, server.file);
464
+ }
465
+ if (affected.size > 0) {
466
+ const serverFiles = [...new Set(affected.values())];
467
+ const names = [...affected.keys()].sort();
468
+ problems.push({
469
+ scope: "repository direct tools",
470
+ file: serverFiles[0] ?? prefixFile,
471
+ detail: `${serverFiles.join(" and ")} ${serverFiles.length === 1 ? "enables" : "enable"} effective directTools ` +
472
+ `for repository MCP server(s) ${names.join(", ")} ` +
473
+ `while ${prefixFile} sets toolPrefix "none". This strips the server name off those tools and leaves ` +
474
+ "nothing to check an approval against. Disable directTools for those servers or use a server-bearing tool prefix."
475
+ });
476
+ }
477
+ }
478
+
433
479
  for (const scope of REPOSITORY_SCOPES) {
434
480
  const file = configPathForScope(scope, options);
435
481
  let config;
@@ -77,6 +77,17 @@ export function attributeDirectTool(toolName, serverNames) {
77
77
  return best;
78
78
  }
79
79
 
80
+ /**
81
+ * Whether a global or per-server direct-tool selection registers at least one
82
+ * direct tool. The adapter accepts `true` or a non-empty list on server entries.
83
+ * @param {unknown} value
84
+ * @returns {boolean}
85
+ */
86
+ export function directToolsEnabled(value) {
87
+ return value === true
88
+ || (Array.isArray(value) && value.some((item) => typeof item === "string" && item.trim().length > 0));
89
+ }
90
+
80
91
  /**
81
92
  * Whether a config's settings put tools on the model under names that cannot be
82
93
  * traced to a server. Both halves are required: without `directTools` every call
@@ -86,5 +97,5 @@ export function attributeDirectTool(toolName, serverNames) {
86
97
  */
87
98
  export function erasesToolOrigin(settings) {
88
99
  if (!settings || typeof settings !== "object") return false;
89
- return settings.directTools === true && settings.toolPrefix === "none";
100
+ return directToolsEnabled(settings.directTools) && settings.toolPrefix === "none";
90
101
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piagent/core",
3
- "version": "1.2.8",
3
+ "version": "1.2.10",
4
4
  "type": "module",
5
5
  "private": true,
6
6
  "license": "MIT",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: piagent-reviewer
3
3
  description: Piagent reviewer for implementation diff, policy, tests, and scope drift
4
- tools: read, grep, find, ls, bash, edit, write, contact_supervisor
4
+ tools: read, grep, find, ls
5
5
  thinking: high
6
6
  systemPromptMode: replace
7
7
  inheritProjectContext: true
@@ -16,7 +16,7 @@ maxSubagentDepth: 0
16
16
 
17
17
  You are `piagent-reviewer`, a disciplined review subagent for Pi Agent Platform projects.
18
18
 
19
- Your job is to review evidence, not to invent issues. Make small corrective edits only when the parent explicitly asks for autofix.
19
+ Your job is to review evidence, not to invent issues. Never edit files or run mutation commands. Report actionable findings so the parent can assign fixes to an explicit writer.
20
20
 
21
21
  Use explicit review lenses from the parent task when provided. If no lenses are provided, cover correctness, tests/verification, and scope drift first; add security/release/package only when relevant to the change.
22
22
 
@@ -33,7 +33,7 @@ Rules:
33
33
  - Do not run destructive commands.
34
34
  - Treat Field Guide/memory as advisory, not as proof.
35
35
  - If review-only conflicts with progress-writing or artifact-writing instructions, review-only wins.
36
- - If a blocker needs a decision, use `contact_supervisor` with `reason: "need_decision"`.
36
+ - Put blockers that need a decision in the final review so the parent can route them.
37
37
  - Do not spawn other subagents.
38
38
 
39
39
  Final output:
@@ -9,9 +9,12 @@ import {
9
9
  buildContextPack,
10
10
  buildTestImpact,
11
11
  contextIndexV2Status,
12
+ ensureContextIndexV2,
12
13
  searchContextIndexV2
13
14
  } from "../packages/piagent-core/extensions/context-engine.js";
15
+ import { loadProjectContextIndexPolicy } from "../packages/piagent-core/extensions/context-index-policy.js";
14
16
 
17
+ const platformRoot = path.resolve(import.meta.dirname, "..");
15
18
  function usage() {
16
19
  return [
17
20
  "Pi Context Engine",
@@ -53,21 +56,6 @@ function parseArgs(argv) {
53
56
  return { command, project, json, tokens, values };
54
57
  }
55
58
 
56
- function projectExcludes(project) {
57
- const target = path.join(project, ".pi", "piagent-profile.json");
58
- try {
59
- const profile = JSON.parse(fs.readFileSync(target, "utf8"));
60
- return [
61
- ...(Array.isArray(profile.protectedPaths) ? profile.protectedPaths : []),
62
- ...(Array.isArray(profile.shellProtectedPaths) ? profile.shellProtectedPaths : []),
63
- ".pi/context-index.json",
64
- ".pi/piagent-state/**"
65
- ];
66
- } catch {
67
- return [".pi/context-index.json", ".pi/piagent-state/**"];
68
- }
69
- }
70
-
71
59
  function print(value, json) {
72
60
  if (json || typeof value === "string") {
73
61
  process.stdout.write(`${typeof value === "string" ? value : JSON.stringify(value, null, 2)}\n`);
@@ -88,21 +76,35 @@ if (!fs.existsSync(parsed.project) || !fs.statSync(parsed.project).isDirectory()
88
76
  }
89
77
 
90
78
  try {
79
+ const contextPolicy = ["status", "show", "rebuild", "build", "refresh", "search", "pack", "impact", "tests"]
80
+ .includes(parsed.command)
81
+ ? loadProjectContextIndexPolicy(platformRoot, parsed.project, {
82
+ profilePath: process.env.PIAGENT_PROFILE
83
+ })
84
+ : undefined;
85
+ const excludePatterns = contextPolicy?.excludePatterns ?? [];
91
86
  if (["status", "show"].includes(parsed.command)) {
92
- print(await contextIndexV2Status(parsed.project), parsed.json);
87
+ print(await contextIndexV2Status(parsed.project, { excludePatterns }), parsed.json);
93
88
  } else if (["rebuild", "build", "refresh"].includes(parsed.command)) {
94
- print(await buildContextIndexV2(parsed.project, { excludePatterns: projectExcludes(parsed.project) }), parsed.json);
89
+ print(await buildContextIndexV2(parsed.project, { excludePatterns }), parsed.json);
95
90
  } else if (parsed.command === "search") {
96
91
  const query = parsed.values.join(" ").trim();
97
92
  if (!query) throw new Error("search requires a query");
98
- print(await searchContextIndexV2(parsed.project, query, { limit: 15 }), parsed.json);
93
+ await ensureContextIndexV2(parsed.project, { excludePatterns, rebuildMissing: true });
94
+ print(await searchContextIndexV2(parsed.project, query, { limit: 15, excludePatterns }), parsed.json);
99
95
  } else if (parsed.command === "pack") {
100
96
  const query = parsed.values.join(" ").trim();
101
97
  if (!query) throw new Error("pack requires a task or query");
102
- const pack = await buildContextPack(parsed.project, query, { budgetTokens: parsed.tokens, includeCode: true });
98
+ await ensureContextIndexV2(parsed.project, { excludePatterns, rebuildMissing: true });
99
+ const pack = await buildContextPack(parsed.project, query, {
100
+ budgetTokens: parsed.tokens,
101
+ includeCode: true,
102
+ excludePatterns
103
+ });
103
104
  print(parsed.json ? pack : pack.text, parsed.json);
104
105
  } else if (["impact", "tests"].includes(parsed.command)) {
105
- print(await buildTestImpact(parsed.project, parsed.values), parsed.json);
106
+ await ensureContextIndexV2(parsed.project, { excludePatterns, rebuildMissing: false });
107
+ print(await buildTestImpact(parsed.project, parsed.values, { excludePatterns }), parsed.json);
106
108
  } else if (["efficiency", "stats", "waste"].includes(parsed.command)) {
107
109
  print(buildContextEfficiencyReport(parsed.project), parsed.json);
108
110
  } else {
@@ -302,7 +302,7 @@ function summarizeMigration(report, dryRun) {
302
302
 
303
303
  function runProjectMigration(project, options, activePlatformRoot) {
304
304
  const migrator = path.join(activePlatformRoot, "scripts/migrate-project-state.mjs");
305
- if (!options.dryRun && !fs.existsSync(migrator)) {
305
+ if (!fs.existsSync(migrator)) {
306
306
  fail(`the updated helper has no project migrator at ${migrator}`);
307
307
  }
308
308
  const args = options.dryRun
@@ -52,6 +52,7 @@ required_files=(
52
52
  "$ROOT/packages/piagent-core/package.json"
53
53
  "$ROOT/packages/piagent-core/extensions/piagent-guard.ts"
54
54
  "$ROOT/packages/piagent-core/extensions/context-engine.js"
55
+ "$ROOT/packages/piagent-core/extensions/context-index-policy.js"
55
56
  "$ROOT/packages/piagent-core/extensions/policy-core.js"
56
57
  "$ROOT/packages/piagent-core/extensions/redaction-core.js"
57
58
  "$ROOT/packages/piagent-core/extensions/runtime-evidence.js"
@@ -545,6 +546,7 @@ fi
545
546
 
546
547
  node --check "$ROOT/packages/piagent-core/extensions/piagent-guard.ts" >/dev/null
547
548
  node --check "$ROOT/packages/piagent-core/extensions/context-engine.js" >/dev/null
549
+ node --check "$ROOT/packages/piagent-core/extensions/context-index-policy.js" >/dev/null
548
550
  node --check "$ROOT/packages/piagent-core/extensions/policy-core.js" >/dev/null
549
551
  node --check "$ROOT/packages/piagent-core/extensions/redaction-core.js" >/dev/null
550
552
  node --check "$ROOT/packages/piagent-core/extensions/runtime-evidence.js" >/dev/null