@piagent/platform 1.2.2 → 1.2.4
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 +28 -0
- package/README.md +34 -27
- package/catalog/capabilities.json +8 -8
- package/docs/auto-delegation-policy.md +4 -4
- package/docs/command-reference-vietnamese.md +123 -86
- package/docs/context-window-policy.md +7 -7
- package/docs/distribution-standard.md +49 -11
- package/docs/herdr-workflow.md +3 -2
- package/docs/memory-policy.md +6 -3
- package/docs/operator-manual-vietnamese.md +81 -72
- package/docs/package-architecture-notes.md +6 -6
- package/docs/project-adapters.md +4 -4
- package/docs/project-onboarding.md +12 -11
- package/docs/publishing-for-teams.md +2 -2
- package/docs/quickstart-vietnamese.md +25 -23
- package/docs/release-install-policy.md +23 -7
- package/docs/runtime-harness-standard.md +7 -7
- package/docs/runtime-policy-design.md +1 -1
- package/docs/runtime-quality-baseline.md +1 -1
- package/docs/security-threat-model.md +1 -1
- package/docs/subagent-orchestration-capabilities.md +3 -3
- package/docs/subagents-and-multiagent.md +4 -4
- package/docs/task-implementation-contract.md +4 -4
- package/docs/task-lifecycle-vietnamese.md +2 -2
- package/docs/team-onboarding.md +39 -19
- package/docs/usage-observability.md +13 -12
- package/docs/workflow-recipes.md +12 -9
- package/docs-site/architecture.html +1 -1
- package/docs-site/commands.html +19 -15
- package/docs-site/content/commands.html +18 -14
- package/docs-site/content/permissions.html +11 -11
- package/docs-site/content/quickstart.html +12 -11
- package/docs-site/content/team.html +1 -1
- package/docs-site/content/workflows.html +17 -17
- package/docs-site/index.html +1 -1
- package/docs-site/mcp-approval.html +1 -1
- package/docs-site/mcp-auth.html +1 -1
- package/docs-site/mcp-servers.html +1 -1
- package/docs-site/mcp.html +1 -1
- package/docs-site/permissions.html +12 -12
- package/docs-site/profiles.html +1 -1
- package/docs-site/quickstart.html +13 -12
- package/docs-site/runtime.html +1 -1
- package/docs-site/scope.html +1 -1
- package/docs-site/security.html +1 -1
- package/docs-site/team.html +2 -2
- package/docs-site/workflows.html +18 -18
- package/package.json +1 -1
- package/packages/piagent-core/README.md +27 -27
- package/packages/piagent-core/extensions/piagent-guard.ts +1055 -190
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/prompts/be-to-fe.md +3 -3
- package/packages/piagent-core/prompts/discuss.md +2 -2
- package/packages/piagent-core/prompts/plan.md +1 -1
- package/packages/piagent-core/prompts/platform-improve.md +1 -1
- package/packages/piagent-core/prompts/pr.md +1 -1
- package/packages/piagent-core/prompts/review.md +1 -1
- package/packages/piagent-core/prompts/scout.md +2 -2
- package/packages/piagent-core/prompts/task.md +2 -2
- package/scripts/init-project.sh +4 -3
- package/scripts/install-global.sh +47 -1
- package/scripts/profile-doctor.sh +1 -1
- package/scripts/setup.sh +4 -3
- package/scripts/team-doctor.sh +6 -8
- package/scripts/update-global.mjs +207 -11
- package/scripts/verify-local.sh +18 -17
- package/templates/project/.pi/context-index.json +2 -2
- package/templates/project/.pi/project-context.md +3 -3
- package/templates/project/AGENTS.md +4 -4
- package/packages/piagent-core/prompts/memory-policy.md +0 -52
- package/packages/piagent-core/prompts/model-options.md +0 -68
- package/packages/piagent-core/prompts/onboard-project.md +0 -147
- package/packages/piagent-core/prompts/piagent-commands.md +0 -69
|
@@ -20,11 +20,11 @@ Use this for tasks like:
|
|
|
20
20
|
|
|
21
21
|
Mandatory flow:
|
|
22
22
|
|
|
23
|
-
0. Call `piagent_context_preflight` with `workflow=be-to-fe`. If it recommends `fresh-session`, stop loading context in this session and tell the user to use `/fresh
|
|
23
|
+
0. Call `piagent_context_preflight` with `workflow=be-to-fe`. If it recommends `fresh-session`, stop loading context in this session and tell the user to use `/piagent-session fresh be-to-fe <request>` unless this command already runs in a fresh session.
|
|
24
24
|
1. Call `piagent_context` with `detail=full` and confirm profile/protected backend paths.
|
|
25
25
|
2. Call `piagent_orchestration_policy`; keep the workflow solo-first unless bounded BE/FE scouting or review is worth the extra token/tool cost.
|
|
26
26
|
3. Call `piagent_memory_status`; search memory or Field Guide for prior BE/FE mapping decisions if relevant, record citations with `piagent_memory_citation_record`, then verify against current BE/FE files.
|
|
27
|
-
4. Read `.pi/project-context.md`. If pending, stop and ask for `/onboard-project`.
|
|
27
|
+
4. Read `.pi/project-context.md`. If pending, stop and ask for `/onboard-project run`.
|
|
28
28
|
5. Read required context from the active profile and call `piagent_context_budget` for BE/FE files that look large.
|
|
29
29
|
6. Classify the task:
|
|
30
30
|
- BE scout: read-only.
|
|
@@ -67,7 +67,7 @@ Mandatory flow:
|
|
|
67
67
|
|
|
68
68
|
For generic projects, use profile `be-readonly-fe` when the repo policy is “BE scout only, FE write allowed”.
|
|
69
69
|
|
|
70
|
-
Do not ask the user to paste the mandatory flow. The platform prompt already contains it. If the user asks only for scout/audit, stay read-only and prefer `/scout` or `/fresh
|
|
70
|
+
Do not ask the user to paste the mandatory flow. The platform prompt already contains it. If the user asks only for scout/audit, stay read-only and prefer `/workflow scout` or `/piagent-session fresh scout`.
|
|
71
71
|
|
|
72
72
|
Final output:
|
|
73
73
|
|
|
@@ -13,7 +13,7 @@ Operating rules:
|
|
|
13
13
|
|
|
14
14
|
1. Call `piagent_context` first when available.
|
|
15
15
|
2. Call `piagent_memory_status`; use memory only to avoid repeated context questions, not as authority.
|
|
16
|
-
3. Read `.pi/project-context.md` if available; if it is still pending, recommend `/onboard-project` before implementation.
|
|
16
|
+
3. Read `.pi/project-context.md` if available; if it is still pending, recommend `/onboard-project run` before implementation.
|
|
17
17
|
4. Inspect relevant files/docs before asking; do not ask what can be answered from the project.
|
|
18
18
|
5. Identify the next unresolved decision, dependency, constraint, or risk.
|
|
19
19
|
6. Ask at most 3 focused questions per round.
|
|
@@ -32,4 +32,4 @@ Final output when clear:
|
|
|
32
32
|
- implementation approach
|
|
33
33
|
- required context
|
|
34
34
|
- verify commands
|
|
35
|
-
- recommended next command: `/plan` or `/task`
|
|
35
|
+
- recommended next command: `/workflow plan` or `/workflow task`
|
|
@@ -14,7 +14,7 @@ Rules:
|
|
|
14
14
|
1. Call `piagent_context`.
|
|
15
15
|
2. Call `piagent_orchestration_policy`; default to solo-first and choose review lenses/model roles before considering subagents.
|
|
16
16
|
3. Call `piagent_memory_status`; search memory or Field Guide if it can reduce re-scouting, but treat it as advisory.
|
|
17
|
-
4. Read `.pi/project-context.md`; if it is missing or still pending, recommend `/onboard-project` before implementation.
|
|
17
|
+
4. Read `.pi/project-context.md`; if it is missing or still pending, recommend `/onboard-project run` before implementation.
|
|
18
18
|
5. Read required context.
|
|
19
19
|
6. Identify protected paths and external/high-risk actions.
|
|
20
20
|
7. Decide whether subagents are useful enough to justify extra token/tool cost. If the bundled `pi-subagents` parent skill is available, use it for delegation patterns and safety boundaries. If `pi-subagents`/`subagent(...)` is available, use `piagent-scout` for independent read-only repo/spec mapping before medium/large plans. Use builtin `context-builder` when the task needs a handoff context/meta-prompt. Use `piagent-oracle` for architecture/risk challenge when the decision is non-obvious. Continue single-agent for tiny plans or unavailable subagent tooling.
|
|
@@ -23,7 +23,7 @@ Mandatory flow:
|
|
|
23
23
|
1. Call `piagent_context` with `detail=full` and inspect the active project profile.
|
|
24
24
|
2. Call `piagent_orchestration_policy`; keep the platform change solo-first unless bounded scout/planning/review lanes are useful.
|
|
25
25
|
3. Call `piagent_memory_status`; if prior decisions or Field Guide notes are relevant, search memory and record citations with `piagent_memory_citation_record`.
|
|
26
|
-
4. Read `.pi/project-context.md`. If pending, stop and ask the user to run `/onboard-project`.
|
|
26
|
+
4. Read `.pi/project-context.md`. If pending, stop and ask the user to run `/onboard-project run`.
|
|
27
27
|
5. Read the required context declared by the active profile. Before loading large files, call `piagent_context_budget`.
|
|
28
28
|
6. Classify the task risk lane and create a task contract with `piagent_task_start`, including review lenses and a compact workPlan, before source edits.
|
|
29
29
|
7. Decide whether bounded subagents are useful enough to justify extra token/tool cost:
|
|
@@ -16,7 +16,7 @@ Rules:
|
|
|
16
16
|
1. Do not merge. Do not publish, release, deploy, or tag.
|
|
17
17
|
2. Use normal shell Git/GitHub commands so the piagent guard and confirmation gates remain active.
|
|
18
18
|
3. Start with `git status --short`, `git branch --show-current`, and `git remote -v`.
|
|
19
|
-
4. If there are uncommitted changes, stop and ask the operator to run `/commit` first, unless the operator explicitly asks this command to include a commit.
|
|
19
|
+
4. If there are uncommitted changes, stop and ask the operator to run `/workflow commit` first, unless the operator explicitly asks this command to include a commit.
|
|
20
20
|
5. Run the relevant verification command before opening the PR. At minimum use `git diff --check`; for source or policy changes, run the project verify/test/typecheck commands that apply.
|
|
21
21
|
6. If the branch is not pushed, ask for explicit operator confirmation before `git push -u origin <branch>`.
|
|
22
22
|
7. Create the PR only after the operator explicitly confirms the external GitHub action. Prefer `gh pr create --draft` with a clear title/body derived from the current diff and commits, unless the operator explicitly asks for a ready-for-review PR.
|
|
@@ -11,7 +11,7 @@ $ARGUMENTS
|
|
|
11
11
|
|
|
12
12
|
Rules:
|
|
13
13
|
|
|
14
|
-
0. Call `piagent_context_preflight` with `workflow=review` for large diffs or long requests. If it recommends `fresh-session`, prefer `/fresh
|
|
14
|
+
0. Call `piagent_context_preflight` with `workflow=review` for large diffs or long requests. If it recommends `fresh-session`, prefer `/piagent-session fresh scout` for read-only evidence gathering or ask the user to resume in a fresh session.
|
|
15
15
|
1. Call `piagent_context`.
|
|
16
16
|
2. Call `piagent_orchestration_policy` and select explicit review lenses before reading the diff deeply.
|
|
17
17
|
3. Read `.pi/project-context.md` if available.
|
|
@@ -14,11 +14,11 @@ Use this when the user needs evidence before deciding whether to implement. Do n
|
|
|
14
14
|
Preflight first:
|
|
15
15
|
|
|
16
16
|
1. Call `piagent_context_preflight` with `workflow=scout`.
|
|
17
|
-
2. If it recommends `fresh-session`, stop loading context in this session and tell the user to use `/fresh
|
|
17
|
+
2. If it recommends `fresh-session`, stop loading context in this session and tell the user to use `/piagent-session fresh scout <request>` unless this command already runs in a fresh session.
|
|
18
18
|
3. Call `piagent_context` with `detail=full`.
|
|
19
19
|
4. Call `piagent_orchestration_policy`; keep scout solo-first unless independent read-only lanes are useful.
|
|
20
20
|
5. Call `piagent_memory_status`; cite relevant memory or Field Guide only with `piagent_memory_citation_record`, then verify against current repo files.
|
|
21
|
-
6. Read `.pi/project-context.md`; if pending, stop and ask for `/onboard-project`.
|
|
21
|
+
6. Read `.pi/project-context.md`; if pending, stop and ask for `/onboard-project run`.
|
|
22
22
|
7. Create a read-only task contract with `piagent_task_start` before broad scouting:
|
|
23
23
|
- risk lane;
|
|
24
24
|
- expected output;
|
|
@@ -11,11 +11,11 @@ $ARGUMENTS
|
|
|
11
11
|
|
|
12
12
|
Mandatory flow:
|
|
13
13
|
|
|
14
|
-
0. Call `piagent_context_preflight` with `workflow=task`. If it recommends `fresh-session`, stop loading context in this session and tell the user to use `/fresh
|
|
14
|
+
0. Call `piagent_context_preflight` with `workflow=task`. If it recommends `fresh-session`, stop loading context in this session and tell the user to use `/piagent-session fresh task <request>` unless this command already runs in a fresh session.
|
|
15
15
|
1. Call `piagent_context` and read the project profile/runtime policy.
|
|
16
16
|
2. Call `piagent_orchestration_policy` and keep the task solo-first unless the policy and task shape make bounded subagents clearly useful.
|
|
17
17
|
3. Call `piagent_memory_status`. If memory or the Field Guide is enabled and relevant to the task, search/read it as advisory context, record citations with `piagent_memory_citation_record`, then verify against current repo files.
|
|
18
|
-
4. Call `piagent_context_index_status` when available, then read `.pi/project-context.md`. If `.pi/project-context.md` is missing or still says `Generated: not yet`, stop and ask the user to run `/onboard-project` after login/model selection before implementation.
|
|
18
|
+
4. Call `piagent_context_index_status` when available, then read `.pi/project-context.md`. If `.pi/project-context.md` is missing or still says `Generated: not yet`, stop and ask the user to run `/onboard-project run` after login/model selection before implementation.
|
|
19
19
|
- Use `piagent_context_index_search` for navigation hints when the task touches an unfamiliar module/tech/risk area.
|
|
20
20
|
- Treat context-index hits as advisory; open and verify cited files before editing.
|
|
21
21
|
5. Build a Task Implementation Contract with `piagent_task_start` before editing:
|
package/scripts/init-project.sh
CHANGED
|
@@ -315,10 +315,11 @@ echo "Next:"
|
|
|
315
315
|
echo " cd \"$PROJECT_PATH\""
|
|
316
316
|
echo " pi"
|
|
317
317
|
echo " /login # first time only"
|
|
318
|
-
echo " /onboard
|
|
318
|
+
echo " /onboard # open onboarding menu/status"
|
|
319
|
+
echo " /onboard run # first project-read after model selection"
|
|
319
320
|
echo " /mcp # MCP servers come from the global install, not from this project"
|
|
320
|
-
echo " /context
|
|
321
|
-
echo " /memory
|
|
321
|
+
echo " /context index # inspect compact project context index"
|
|
322
|
+
echo " /memory # inspect project memory policy when needed"
|
|
322
323
|
echo
|
|
323
324
|
echo "Note: .mcp.json and .pi/mcp.json are written empty on purpose. They exist so this"
|
|
324
325
|
echo "project can add its own servers later; the shared baseline lives in the global"
|
|
@@ -178,6 +178,50 @@ run_cmd() {
|
|
|
178
178
|
fi
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
resolve_user_package_path() {
|
|
182
|
+
local source="$1"
|
|
183
|
+
local pi_agent_dir="${PI_CODING_AGENT_DIR:-"${HOME}/.pi/agent"}"
|
|
184
|
+
node --input-type=module - "$pi_agent_dir" "$source" <<'NODE'
|
|
185
|
+
import path from "node:path";
|
|
186
|
+
|
|
187
|
+
const base = process.argv[2];
|
|
188
|
+
const source = process.argv[3];
|
|
189
|
+
console.log(path.resolve(base, source));
|
|
190
|
+
NODE
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
is_piagent_platform_source() {
|
|
194
|
+
local source="$1"
|
|
195
|
+
case "$source" in
|
|
196
|
+
git:github.com/Vt-mmm/piagent|git:github.com/Vt-mmm/piagent@*|https://github.com/Vt-mmm/piagent*|npm:@piagent/platform|npm:@piagent/platform@*)
|
|
197
|
+
return 0
|
|
198
|
+
;;
|
|
199
|
+
npm:*|git:*|http:*|https:*)
|
|
200
|
+
return 1
|
|
201
|
+
;;
|
|
202
|
+
esac
|
|
203
|
+
|
|
204
|
+
local resolved_source
|
|
205
|
+
resolved_source="$(resolve_user_package_path "$source" 2>/dev/null || true)"
|
|
206
|
+
[[ "$resolved_source" == "$PLATFORM_ROOT" ]]
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
remove_existing_piagent_platform_sources() {
|
|
210
|
+
local source
|
|
211
|
+
while IFS= read -r source; do
|
|
212
|
+
[[ -n "$source" ]] || continue
|
|
213
|
+
if is_piagent_platform_source "$source"; then
|
|
214
|
+
run_cmd pi remove "$source"
|
|
215
|
+
fi
|
|
216
|
+
done < <(
|
|
217
|
+
pi list --approve 2>/dev/null | awk '
|
|
218
|
+
$0 == "User packages:" { in_user = 1; next }
|
|
219
|
+
$0 == "Project packages:" { in_user = 0; next }
|
|
220
|
+
in_user && $0 ~ /^ [^ ]/ { sub(/^ /, ""); print }
|
|
221
|
+
'
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
|
|
181
225
|
verify_npm_integrity() {
|
|
182
226
|
local source="$1"
|
|
183
227
|
local expected="$2"
|
|
@@ -496,6 +540,8 @@ if [[ -n "$RESOLVED_PACKAGE_COMMIT" ]]; then
|
|
|
496
540
|
echo " resolvedCommit: $RESOLVED_PACKAGE_COMMIT"
|
|
497
541
|
fi
|
|
498
542
|
echo " source: $PACKAGE_SOURCE"
|
|
543
|
+
echo "Removing previous Pi Agent Platform package registrations:"
|
|
544
|
+
remove_existing_piagent_platform_sources
|
|
499
545
|
run_cmd pi install "$PACKAGE_SOURCE"
|
|
500
546
|
|
|
501
547
|
if [[ "$WITH_MCP" == true ]]; then
|
|
@@ -562,4 +608,4 @@ if [[ "$WITH_MCP" == true ]]; then
|
|
|
562
608
|
echo " /mcp # inspect MCP servers; authenticate Figma/GitHub only when needed"
|
|
563
609
|
fi
|
|
564
610
|
echo " /subagents-doctor"
|
|
565
|
-
echo " /task Implement <task> # parent may auto-delegate scout/planner/reviewer"
|
|
611
|
+
echo " /workflow task Implement <task> # parent may auto-delegate scout/planner/reviewer"
|
|
@@ -198,7 +198,7 @@ if (contextIndex.enabled && contextIndex.writePolicy !== "off") {
|
|
|
198
198
|
if (relativeIndexPath.startsWith("..") || path.isAbsolute(relativeIndexPath)) {
|
|
199
199
|
errors.push(`contextIndex.path escapes project root: ${contextIndex.path}`);
|
|
200
200
|
} else if (!fs.existsSync(indexPath)) {
|
|
201
|
-
warnings.push(`context index missing: ${contextIndex.path}; run /onboard
|
|
201
|
+
warnings.push(`context index missing: ${contextIndex.path}; run /onboard run or record piagent_context_index_record`);
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
204
|
|
package/scripts/setup.sh
CHANGED
|
@@ -452,12 +452,13 @@ if [[ "$DO_PROJECT" == true ]]; then
|
|
|
452
452
|
echo " /scoped-models # optional: edit Ctrl+P model cycle scope"
|
|
453
453
|
echo " /mcp # inspect MCP servers; run /mcp setup for guided changes"
|
|
454
454
|
echo " /subagents-doctor # inspect subagent setup"
|
|
455
|
-
echo " /onboard
|
|
456
|
-
echo " /
|
|
455
|
+
echo " /onboard # open onboarding menu/status"
|
|
456
|
+
echo " /onboard run # first project-read snapshot before implementation"
|
|
457
|
+
echo " /memory # inspect project memory policy when needed"
|
|
457
458
|
fi
|
|
458
459
|
echo
|
|
459
460
|
echo "Daily flow after setup:"
|
|
460
461
|
echo " herdr # optional"
|
|
461
462
|
echo " cd <project>"
|
|
462
463
|
echo " pi"
|
|
463
|
-
echo " /task Implement <task> # parent may auto-delegate scout/planner/reviewer"
|
|
464
|
+
echo " /workflow task Implement <task> # parent may auto-delegate scout/planner/reviewer"
|
package/scripts/team-doctor.sh
CHANGED
|
@@ -149,8 +149,6 @@ if (!runtime.teamRolloutReady) {
|
|
|
149
149
|
for (const rel of [
|
|
150
150
|
"package.json",
|
|
151
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
152
|
"packages/piagent-core/prompts/platform-improve.md",
|
|
155
153
|
"packages/piagent-core/prompts/be-to-fe.md",
|
|
156
154
|
"packages/piagent-core/prompts/task.md",
|
|
@@ -374,10 +372,10 @@ const projectContextPath = path.join(projectPath, ".pi", "project-context.md");
|
|
|
374
372
|
if (fs.existsSync(projectContextPath)) {
|
|
375
373
|
const projectContext = fs.readFileSync(projectContextPath, "utf8");
|
|
376
374
|
if (/Generated:\s*not yet/i.test(projectContext)) {
|
|
377
|
-
warnings.push("project onboarding snapshot is still pending; run /onboard
|
|
375
|
+
warnings.push("project onboarding snapshot is still pending; run /onboard run in Pi after login/model selection");
|
|
378
376
|
}
|
|
379
377
|
} else {
|
|
380
|
-
warnings.push("project has no .pi/project-context.md; run setup/init or /onboard
|
|
378
|
+
warnings.push("project has no .pi/project-context.md; run setup/init or /onboard run");
|
|
381
379
|
}
|
|
382
380
|
|
|
383
381
|
const contextIndexConfig = projectProfile && typeof projectProfile.contextIndex === "object" && !Array.isArray(projectProfile.contextIndex)
|
|
@@ -402,8 +400,8 @@ if (contextIndexEscapes) {
|
|
|
402
400
|
const edges = Array.isArray(contextIndex.edges) ? contextIndex.edges : [];
|
|
403
401
|
const citations = Array.isArray(contextIndex.citations) ? contextIndex.citations : [];
|
|
404
402
|
if (contextIndex.schemaVersion !== 1) warnings.push("project context index schemaVersion should be 1");
|
|
405
|
-
if (
|
|
406
|
-
warnings.push("project context index is still pending; run /onboard
|
|
403
|
+
if (/Pending\.\s+Run \/onboard(?: run)? after login\/model selection/i.test(String(contextIndex.summary ?? ""))) {
|
|
404
|
+
warnings.push("project context index is still pending; run /onboard run after login/model selection");
|
|
407
405
|
}
|
|
408
406
|
if (nodes.length === 0) warnings.push("project context index has no nodes yet");
|
|
409
407
|
if (contextIndex?.policy?.requireCitations !== false && citations.length === 0) warnings.push("project context index has no citations yet");
|
|
@@ -411,13 +409,13 @@ if (contextIndexEscapes) {
|
|
|
411
409
|
if (edges.length > (contextIndex?.policy?.maxEdges ?? 240)) warnings.push("project context index has more edges than policy maxEdges");
|
|
412
410
|
}
|
|
413
411
|
} else {
|
|
414
|
-
warnings.push(`project has no ${contextIndexRelativePath}; run setup/init or /onboard
|
|
412
|
+
warnings.push(`project has no ${contextIndexRelativePath}; run setup/init or /onboard run`);
|
|
415
413
|
}
|
|
416
414
|
|
|
417
415
|
const memorySummaryPath = path.join(projectPath, ".pi", "memory", "memory_summary.md");
|
|
418
416
|
const memoryHandbookPath = path.join(projectPath, ".pi", "memory", "MEMORY.md");
|
|
419
417
|
if (!fs.existsSync(memorySummaryPath) || !fs.existsSync(memoryHandbookPath)) {
|
|
420
|
-
warnings.push("project has no .pi/memory scaffold; run setup/init or use /memory
|
|
418
|
+
warnings.push("project has no .pi/memory scaffold; run setup/init or use /memory before relying on project memory");
|
|
421
419
|
}
|
|
422
420
|
|
|
423
421
|
if (strictShare) {
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
// it asked for.
|
|
18
18
|
|
|
19
19
|
import fs from "node:fs";
|
|
20
|
+
import os from "node:os";
|
|
20
21
|
import path from "node:path";
|
|
21
22
|
import { spawnSync } from "node:child_process";
|
|
22
23
|
import { fileURLToPath } from "node:url";
|
|
@@ -39,13 +40,18 @@ Options:
|
|
|
39
40
|
--dry-run Print every command that would run, change nothing
|
|
40
41
|
--version <x.y.z> Update to an exact helper version instead of latest
|
|
41
42
|
--force Reinstall even when already on the target version
|
|
42
|
-
--project <path>
|
|
43
|
+
--project <path> Optional: after the global update, migrate and doctor this project
|
|
43
44
|
--no-host Do not touch the Pi host
|
|
44
45
|
--no-package Do not reinstall the Pi package
|
|
46
|
+
--no-migrate With --project, skip project state migration
|
|
47
|
+
--npm-prefix <path> Use this npm global prefix for host/helper installs
|
|
45
48
|
-h, --help Show this help
|
|
46
49
|
|
|
47
50
|
Anything after -- is passed through to piagent-install, for example:
|
|
48
51
|
piagent-update -- --no-mcp
|
|
52
|
+
|
|
53
|
+
Bootstrap a machine that does not have piagent-update yet:
|
|
54
|
+
npm exec -y --package @piagent/platform@X.Y.Z -- piagent-update --version X.Y.Z --force
|
|
49
55
|
`;
|
|
50
56
|
}
|
|
51
57
|
|
|
@@ -81,6 +87,8 @@ function parseArguments(argv) {
|
|
|
81
87
|
project: undefined,
|
|
82
88
|
host: true,
|
|
83
89
|
package: true,
|
|
90
|
+
migrate: true,
|
|
91
|
+
npmPrefix: undefined,
|
|
84
92
|
passthrough: []
|
|
85
93
|
};
|
|
86
94
|
|
|
@@ -111,6 +119,15 @@ function parseArguments(argv) {
|
|
|
111
119
|
case "--no-package":
|
|
112
120
|
options.package = false;
|
|
113
121
|
break;
|
|
122
|
+
case "--no-migrate":
|
|
123
|
+
options.migrate = false;
|
|
124
|
+
break;
|
|
125
|
+
case "--npm-prefix": {
|
|
126
|
+
const value = argv[index += 1];
|
|
127
|
+
if (!value) fail("--npm-prefix needs a path", EXIT_USAGE);
|
|
128
|
+
options.npmPrefix = value;
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
114
131
|
case "--version": {
|
|
115
132
|
const value = normalizeVersion(argv[index += 1]);
|
|
116
133
|
if (!isReleaseVersion(value)) fail(`--version needs an exact release such as 1.1.4, got ${argv[index] ?? "nothing"}`, EXIT_USAGE);
|
|
@@ -140,6 +157,167 @@ function run(command, args, { capture = false } = {}) {
|
|
|
140
157
|
return { ok: result.status === 0, stdout: result.stdout ?? "", stderr: result.stderr ?? "" };
|
|
141
158
|
}
|
|
142
159
|
|
|
160
|
+
function expandHome(value) {
|
|
161
|
+
if (value === "~") return os.homedir();
|
|
162
|
+
if (value?.startsWith("~/")) return path.join(os.homedir(), value.slice(2));
|
|
163
|
+
return value;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function configuredNpmPrefix() {
|
|
167
|
+
const result = run("npm", ["config", "get", "prefix"], { capture: true });
|
|
168
|
+
if (!result.ok) return undefined;
|
|
169
|
+
const prefix = result.stdout.trim();
|
|
170
|
+
return prefix && prefix !== "undefined" && prefix !== "null" ? path.resolve(expandHome(prefix)) : undefined;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function fallbackNpmRoot(prefix) {
|
|
174
|
+
return process.platform === "win32"
|
|
175
|
+
? path.join(prefix, "node_modules")
|
|
176
|
+
: path.join(prefix, "lib", "node_modules");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function npmRoot(prefix) {
|
|
180
|
+
const args = ["root", "-g"];
|
|
181
|
+
if (prefix) args.push("--prefix", prefix);
|
|
182
|
+
const result = run("npm", args, { capture: true });
|
|
183
|
+
if (result.ok && result.stdout.trim()) return path.resolve(expandHome(result.stdout.trim()));
|
|
184
|
+
return prefix ? fallbackNpmRoot(prefix) : undefined;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function nearestExistingPath(target) {
|
|
188
|
+
let current = target;
|
|
189
|
+
while (current && !fs.existsSync(current)) {
|
|
190
|
+
const parent = path.dirname(current);
|
|
191
|
+
if (parent === current) return undefined;
|
|
192
|
+
current = parent;
|
|
193
|
+
}
|
|
194
|
+
return current || undefined;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function canWriteOrCreate(target) {
|
|
198
|
+
const nearest = nearestExistingPath(target);
|
|
199
|
+
if (!nearest) return false;
|
|
200
|
+
try {
|
|
201
|
+
fs.accessSync(nearest, fs.constants.W_OK);
|
|
202
|
+
return true;
|
|
203
|
+
} catch {
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function userNpmPrefix() {
|
|
209
|
+
return path.join(os.homedir(), ".pi", "npm-global");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function binForPrefix(prefix) {
|
|
213
|
+
return process.platform === "win32" ? prefix : path.join(prefix, "bin");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function prependPath(directory) {
|
|
217
|
+
process.env.PATH = `${directory}${path.delimiter}${process.env.PATH ?? ""}`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function resolveNpmInstallTarget(options, needsGlobalInstall) {
|
|
221
|
+
const explicitPrefix = options.npmPrefix ? path.resolve(expandHome(options.npmPrefix)) : undefined;
|
|
222
|
+
if (explicitPrefix) {
|
|
223
|
+
if (!options.dryRun) fs.mkdirSync(explicitPrefix, { recursive: true });
|
|
224
|
+
return {
|
|
225
|
+
prefix: explicitPrefix,
|
|
226
|
+
root: npmRoot(explicitPrefix),
|
|
227
|
+
bin: binForPrefix(explicitPrefix),
|
|
228
|
+
reason: "explicit"
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const defaultPrefix = configuredNpmPrefix();
|
|
233
|
+
const defaultRoot = npmRoot(defaultPrefix);
|
|
234
|
+
if (!needsGlobalInstall || (defaultRoot && canWriteOrCreate(defaultRoot))) {
|
|
235
|
+
return { prefix: undefined, root: defaultRoot, bin: undefined, reason: "default" };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const prefix = userNpmPrefix();
|
|
239
|
+
if (!options.dryRun) fs.mkdirSync(prefix, { recursive: true });
|
|
240
|
+
return {
|
|
241
|
+
prefix,
|
|
242
|
+
root: npmRoot(prefix),
|
|
243
|
+
bin: binForPrefix(prefix),
|
|
244
|
+
reason: "user-prefix",
|
|
245
|
+
blockedRoot: defaultRoot
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function npmInstallArgs(specifier, npmTarget) {
|
|
250
|
+
const args = ["install", "-g", "--ignore-scripts"];
|
|
251
|
+
if (npmTarget.prefix) args.push("--prefix", npmTarget.prefix);
|
|
252
|
+
args.push(specifier);
|
|
253
|
+
return args;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function helperRootFromNpmTarget(npmTarget) {
|
|
257
|
+
return npmTarget.root ? path.join(npmTarget.root, "@piagent", "platform") : undefined;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function helperVersionAt(root) {
|
|
261
|
+
return root ? readJson(path.join(root, "package.json"))?.version : undefined;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function resolveInstalledHelperRoot(targetHelper, npmTarget) {
|
|
265
|
+
const npmHelperRoot = helperRootFromNpmTarget(npmTarget);
|
|
266
|
+
if (helperVersionAt(npmHelperRoot) === targetHelper) return npmHelperRoot;
|
|
267
|
+
if (helperVersionAt(platformRoot) === targetHelper) return platformRoot;
|
|
268
|
+
const checked = [];
|
|
269
|
+
for (const root of [npmHelperRoot, platformRoot]) {
|
|
270
|
+
if (!root || checked.some((entry) => entry.root === root)) continue;
|
|
271
|
+
checked.push({ root, version: helperVersionAt(root) ?? "nothing" });
|
|
272
|
+
}
|
|
273
|
+
const details = checked.map((entry) => `${entry.root} reports ${entry.version}`).join("; ");
|
|
274
|
+
fail(`installed ${HELPER_PACKAGE}@${targetHelper}, but ${details || "the helper paths did not report that version"}. `
|
|
275
|
+
+ "The Pi package was left untouched; rerun once the helper is correct.");
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function parseJsonOutput(result, label) {
|
|
279
|
+
try {
|
|
280
|
+
return JSON.parse(result.stdout.trim() || "{}");
|
|
281
|
+
} catch {
|
|
282
|
+
fail(`${label} did not return valid JSON:\n${result.stdout.trim()}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function summarizeMigration(report, dryRun) {
|
|
287
|
+
const renamed = report.renamed?.length ?? 0;
|
|
288
|
+
const removed = report.removedOld?.length ?? 0;
|
|
289
|
+
const rewrote = report.rewrote?.length ?? report.wouldRewrite?.length ?? 0;
|
|
290
|
+
const wouldRename = report.wouldRename?.length ?? 0;
|
|
291
|
+
const alreadyMigrated = report.alreadyMigrated?.length ?? 0;
|
|
292
|
+
|
|
293
|
+
if (dryRun) {
|
|
294
|
+
const planned = wouldRename + rewrote + alreadyMigrated;
|
|
295
|
+
return planned > 0
|
|
296
|
+
? `project migration: would update legacy layout (${wouldRename} rename, ${rewrote} rewrite, ${alreadyMigrated} already copied)`
|
|
297
|
+
: "project migration: already current";
|
|
298
|
+
}
|
|
299
|
+
if (report.migrated === false) return "project migration: already current";
|
|
300
|
+
return `project migration: applied (${renamed} rename, ${rewrote} rewrite, ${removed} old path removed)`;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function runProjectMigration(project, options, activePlatformRoot) {
|
|
304
|
+
const migrator = path.join(activePlatformRoot, "scripts/migrate-project-state.mjs");
|
|
305
|
+
if (!options.dryRun && !fs.existsSync(migrator)) {
|
|
306
|
+
fail(`the updated helper has no project migrator at ${migrator}`);
|
|
307
|
+
}
|
|
308
|
+
const args = options.dryRun
|
|
309
|
+
? [migrator, project]
|
|
310
|
+
: [migrator, project, "--apply", "--remove-old"];
|
|
311
|
+
describeStep(options.dryRun, process.execPath, args);
|
|
312
|
+
const result = run(process.execPath, args, { capture: true });
|
|
313
|
+
if (!result.ok) fail(`project migration failed:\n${result.stderr.trim() || result.stdout.trim()}`);
|
|
314
|
+
const report = parseJsonOutput(result, "project migration");
|
|
315
|
+
if ((report.skipped?.length ?? 0) > 0) {
|
|
316
|
+
fail(`project migration skipped protected entries: ${report.skipped.map((entry) => `${entry.path} (${entry.reason})`).join(", ")}`);
|
|
317
|
+
}
|
|
318
|
+
console.log(` ${summarizeMigration(report, options.dryRun)}`);
|
|
319
|
+
}
|
|
320
|
+
|
|
143
321
|
function npmView(specifier, field) {
|
|
144
322
|
const result = run("npm", ["view", specifier, field, "--json"], { capture: true });
|
|
145
323
|
if (!result.ok) return undefined;
|
|
@@ -196,6 +374,9 @@ function main() {
|
|
|
196
374
|
console.log(` helper: ${currentHelper} -> ${targetHelper}${helperNeedsChange ? "" : " (already current)"}`);
|
|
197
375
|
console.log(` host: ${currentHost ?? "not installed"} -> ${targetHost}${hostNeedsChange ? "" : " (already current)"}`);
|
|
198
376
|
console.log(` package: reinstalled from v${targetHelper} as a resolved commit${options.package ? "" : " (skipped)"}`);
|
|
377
|
+
if (options.project) {
|
|
378
|
+
console.log(` project: ${path.resolve(options.project)} (${options.migrate ? "migrate + doctor" : "doctor only"})`);
|
|
379
|
+
}
|
|
199
380
|
|
|
200
381
|
if (options.check) {
|
|
201
382
|
const pending = [helperNeedsChange && "helper", hostNeedsChange && "host"].filter(Boolean);
|
|
@@ -203,7 +384,7 @@ function main() {
|
|
|
203
384
|
return;
|
|
204
385
|
}
|
|
205
386
|
|
|
206
|
-
if (!helperNeedsChange && !hostNeedsChange && !options.package) {
|
|
387
|
+
if (!helperNeedsChange && !hostNeedsChange && !options.package && !options.project) {
|
|
207
388
|
console.log("\nNothing to do.");
|
|
208
389
|
return;
|
|
209
390
|
}
|
|
@@ -215,33 +396,38 @@ function main() {
|
|
|
215
396
|
+ ` bash ${path.join(platformRoot, "scripts/install-global.sh")} --local`);
|
|
216
397
|
}
|
|
217
398
|
|
|
399
|
+
let activePlatformRoot = platformRoot;
|
|
400
|
+
const npmTarget = resolveNpmInstallTarget(options, hostNeedsChange || helperNeedsChange);
|
|
401
|
+
if (npmTarget.bin) prependPath(npmTarget.bin);
|
|
402
|
+
if (npmTarget.reason === "user-prefix") {
|
|
403
|
+
console.log(` npm: default global root is not writable (${npmTarget.blockedRoot ?? "unknown"}); using ${npmTarget.prefix}`);
|
|
404
|
+
} else if (npmTarget.reason === "explicit") {
|
|
405
|
+
console.log(` npm: using explicit global prefix ${npmTarget.prefix}`);
|
|
406
|
+
}
|
|
407
|
+
|
|
218
408
|
// Host first: piagent-install refuses to run against a host that does not match
|
|
219
409
|
// the version its package.json pins, so a helper installed ahead of the host
|
|
220
410
|
// would fail on its very next step.
|
|
221
411
|
if (hostNeedsChange) {
|
|
222
|
-
const args =
|
|
412
|
+
const args = npmInstallArgs(`${HOST_PACKAGE}@${targetHost}`, npmTarget);
|
|
223
413
|
describeStep(options.dryRun, "npm", args);
|
|
224
414
|
if (!options.dryRun && !run("npm", args).ok) fail(`could not install ${HOST_PACKAGE}@${targetHost}`);
|
|
225
415
|
}
|
|
226
416
|
|
|
227
417
|
if (helperNeedsChange) {
|
|
228
|
-
const args =
|
|
418
|
+
const args = npmInstallArgs(`${HELPER_PACKAGE}@${targetHelper}`, npmTarget);
|
|
229
419
|
describeStep(options.dryRun, "npm", args);
|
|
230
420
|
if (!options.dryRun) {
|
|
231
421
|
if (!run("npm", args).ok) fail(`could not install ${HELPER_PACKAGE}@${targetHelper}`);
|
|
232
422
|
// The helper has just replaced itself on disk. Everything after this point
|
|
233
423
|
// runs the newly installed scripts, so confirm they are the ones asked for
|
|
234
424
|
// rather than trusting that npm did what it said.
|
|
235
|
-
|
|
236
|
-
if (landed !== targetHelper) {
|
|
237
|
-
fail(`installed ${HELPER_PACKAGE}@${targetHelper} but ${platformRoot} reports ${landed ?? "nothing"}. `
|
|
238
|
-
+ "The Pi package was left untouched; rerun once the helper is correct.");
|
|
239
|
-
}
|
|
425
|
+
activePlatformRoot = resolveInstalledHelperRoot(targetHelper, npmTarget);
|
|
240
426
|
}
|
|
241
427
|
}
|
|
242
428
|
|
|
243
429
|
if (options.package) {
|
|
244
|
-
const installer = path.join(
|
|
430
|
+
const installer = path.join(activePlatformRoot, "scripts/install-global.sh");
|
|
245
431
|
if (!options.dryRun && !fs.existsSync(installer)) {
|
|
246
432
|
fail(`the updated helper has no installer at ${installer}`);
|
|
247
433
|
}
|
|
@@ -251,12 +437,22 @@ function main() {
|
|
|
251
437
|
}
|
|
252
438
|
|
|
253
439
|
if (options.project) {
|
|
254
|
-
|
|
440
|
+
if (options.migrate) {
|
|
441
|
+
runProjectMigration(options.project, options, activePlatformRoot);
|
|
442
|
+
} else {
|
|
443
|
+
console.log(" project migration: skipped");
|
|
444
|
+
}
|
|
445
|
+
const doctor = path.join(activePlatformRoot, "scripts/team-doctor.sh");
|
|
255
446
|
const args = [doctor, options.project, "--strict-share"];
|
|
256
447
|
describeStep(options.dryRun, "bash", args);
|
|
257
448
|
if (!options.dryRun && !run("bash", args).ok) fail("the team doctor reported a problem");
|
|
258
449
|
}
|
|
259
450
|
|
|
451
|
+
if (npmTarget.reason === "user-prefix") {
|
|
452
|
+
console.log(`\nPATH note: add this once if your shell cannot find piagent-* commands later:`);
|
|
453
|
+
console.log(` export PATH="${npmTarget.bin}:$PATH"`);
|
|
454
|
+
}
|
|
455
|
+
|
|
260
456
|
console.log(options.dryRun
|
|
261
457
|
? `\nDRY RUN: nothing was installed. Rerun without --dry-run to apply v${targetHelper}.`
|
|
262
458
|
: `\nPASS: updated to v${targetHelper}.`);
|