@piagent/platform 1.1.5 → 1.1.6
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 +20 -0
- package/README.md +1 -1
- package/adapters/be-readonly-fe/profile.json +12 -4
- package/docs/command-reference-vietnamese.md +2 -2
- package/docs/distribution-standard.md +7 -7
- package/docs/operator-manual-vietnamese.md +7 -7
- package/docs/project-adapters.md +21 -0
- package/docs/publishing-for-teams.md +1 -0
- package/docs/release-install-policy.md +6 -6
- package/docs/security-threat-model.md +1 -1
- package/docs/team-onboarding.md +3 -3
- package/docs-site/index.html +2 -2
- package/package.json +1 -1
- package/packages/piagent-core/capabilities/capability-core.js +5 -1
- package/packages/piagent-core/extensions/piagent-guard.ts +1 -69
- package/packages/piagent-core/extensions/project-shape.js +285 -0
- package/packages/piagent-core/package.json +1 -1
- package/scripts/init-project.sh +12 -89
- package/scripts/install-global.sh +3 -3
- package/scripts/team-doctor.sh +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
This file records release-facing changes for Pi Agent Platform. Copy the relevant version block into GitHub Releases when publishing a tag.
|
|
4
4
|
|
|
5
|
+
## v1.1.6 - 2026-07-27
|
|
6
|
+
|
|
7
|
+
Monorepo fixes found auditing how the platform scopes itself to a project. Every case was reproduced against a real directory tree before it was changed.
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- `be-readonly-fe` keeps the backend read-only in a monorepo. Its `readOnlyPaths` and `shellProtectedPaths` were anchored at the repository root, so `backend/**` and `apps/api/**` covered a backend sitting at the top of the tree and nothing else. Choosing the profile on a repository whose backend lives in `packages/api`, `apps/backend` or `services/payments` produced a profile that announced the backend was read-only while leaving every file in it writable — the one guarantee the profile exists to make. The backend directories under `apps/`, `packages/` and all of `services/*` are covered now.
|
|
12
|
+
|
|
13
|
+
They are enumerated rather than matched with `**/api/**`, which would have swept in `packages/web/src/api/` — the frontend's own HTTP client — and frozen the half of the repository the profile is meant to leave writable.
|
|
14
|
+
|
|
15
|
+
- The frontend verify command finds a frontend that is not at `frontend/` or `apps/web/`. It fell through to the repository root, where a workspace root has no `type-check` script, so verification reported success having run nothing. `apps/frontend`, `packages/web`, `packages/frontend` and `client` are looked at too.
|
|
16
|
+
|
|
17
|
+
- `apps/backend` is read as a backend. `apps/frontend` counted as a frontend marker while only `apps/api` and `apps/server` counted as backend ones, so a repository that named both halves symmetrically was detected as frontend-only and offered `web-frontend`. `client/` and `server/` at the root were not markers either.
|
|
18
|
+
|
|
19
|
+
- Frontend and backend are detected inside declared workspaces. Nothing read the `workspaces` field or `pnpm-workspace.yaml`, and nothing looked under `packages/`, so a pnpm monorepo fell through every marker to `generic`. Each declared package is now read through its own manifest and layout, which is what identifies `packages/storefront` and `packages/gateway` as the two halves of a fullstack repository. Only a trailing wildcard is expanded, absolute and `..` patterns are refused, and at most 64 packages are read.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Profile detection lives in `packages/piagent-core/extensions/project-shape.js`. `piagent_profile_options` and `scripts/init-project.sh` each carried their own copy of the rules and had drifted; the `apps/backend` gap above was present in both, and a fix to either would have left the other wrong. The file is covered by the capability lock, because steering which profile a project is offered is a way to choose what the guard ends up enforcing.
|
|
24
|
+
|
|
5
25
|
## v1.1.5 - 2026-07-27
|
|
6
26
|
|
|
7
27
|
### Added
|
package/README.md
CHANGED
|
@@ -283,7 +283,7 @@ This repository intentionally excludes:
|
|
|
283
283
|
|
|
284
284
|
## Maturity
|
|
285
285
|
|
|
286
|
-
The current package version is read from package metadata and release tags. Personal machines may follow the unpinned package source when accepting ongoing updates; production/team quickstarts and committed project settings should pin an explicit tag such as `v1.1.
|
|
286
|
+
The current package version is read from package metadata and release tags. Personal machines may follow the unpinned package source when accepting ongoing updates; production/team quickstarts and committed project settings should pin an explicit tag such as `v1.1.6` or a reviewed commit.
|
|
287
287
|
|
|
288
288
|
Ready for:
|
|
289
289
|
|
|
@@ -32,7 +32,11 @@
|
|
|
32
32
|
"api/**",
|
|
33
33
|
"apps/api/**",
|
|
34
34
|
"apps/server/**",
|
|
35
|
-
"
|
|
35
|
+
"apps/backend/**",
|
|
36
|
+
"packages/api/**",
|
|
37
|
+
"packages/server/**",
|
|
38
|
+
"packages/backend/**",
|
|
39
|
+
"services/*/**",
|
|
36
40
|
"**/migrations/**"
|
|
37
41
|
],
|
|
38
42
|
"shellProtectedPaths": [
|
|
@@ -47,7 +51,11 @@
|
|
|
47
51
|
"api/**",
|
|
48
52
|
"apps/api/**",
|
|
49
53
|
"apps/server/**",
|
|
50
|
-
"
|
|
54
|
+
"apps/backend/**",
|
|
55
|
+
"packages/api/**",
|
|
56
|
+
"packages/server/**",
|
|
57
|
+
"packages/backend/**",
|
|
58
|
+
"services/*/**",
|
|
51
59
|
"**/migrations/**",
|
|
52
60
|
"**/node_modules/**",
|
|
53
61
|
"**/dist/**",
|
|
@@ -85,10 +93,10 @@
|
|
|
85
93
|
"test -s README.md"
|
|
86
94
|
],
|
|
87
95
|
"frontendSource": [
|
|
88
|
-
"if test -f frontend/package.json; then cd frontend
|
|
96
|
+
"if test -f frontend/package.json; then cd frontend; elif test -f apps/web/package.json; then cd apps/web; elif test -f apps/frontend/package.json; then cd apps/frontend; elif test -f packages/web/package.json; then cd packages/web; elif test -f packages/frontend/package.json; then cd packages/frontend; elif test -f client/package.json; then cd client; elif test -f package.json; then :; else echo 'No frontend verify command configured; update .pi/piagent-profile.json'; exit 0; fi; npm run type-check --if-present && npm run lint --if-present && npm test --if-present"
|
|
89
97
|
],
|
|
90
98
|
"frontendRuntime": [
|
|
91
|
-
"if test -f frontend/package.json; then cd frontend
|
|
99
|
+
"if test -f frontend/package.json; then cd frontend; elif test -f apps/web/package.json; then cd apps/web; elif test -f apps/frontend/package.json; then cd apps/frontend; elif test -f packages/web/package.json; then cd packages/web; elif test -f packages/frontend/package.json; then cd packages/frontend; elif test -f client/package.json; then cd client; elif test -f package.json; then :; else echo 'No frontend runtime verify command configured; update .pi/piagent-profile.json'; exit 0; fi; npm run test:e2e --if-present"
|
|
92
100
|
]
|
|
93
101
|
},
|
|
94
102
|
"mcpCapabilities": [
|
|
@@ -419,8 +419,8 @@ Các lệnh này chạy ngoài Pi.
|
|
|
419
419
|
| Command | Dùng khi nào |
|
|
420
420
|
|---|---|
|
|
421
421
|
| `npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.82.0` | Cài Pi CLI tương thích với release hiện tại. |
|
|
422
|
-
| `npm install -g --ignore-scripts @piagent/platform@1.1.
|
|
423
|
-
| `pi install git:github.com/Vt-mmm/piagent@v1.1.
|
|
422
|
+
| `npm install -g --ignore-scripts @piagent/platform@1.1.6` | Cài terminal helper `piagent-*` từ release tag hiện tại. |
|
|
423
|
+
| `pi install git:github.com/Vt-mmm/piagent@v1.1.6` | Cài pinned release khi cần reproducible team setup. |
|
|
424
424
|
| `pi install git:github.com/Vt-mmm/piagent` | Cài latest package platform cho máy cá nhân/sandbox. |
|
|
425
425
|
| `piagent-update --check` | Báo version hiện tại vs version sẽ lên cho cả ba thành phần; không cài gì. |
|
|
426
426
|
| `piagent-update` | Full update một lệnh: Pi host → npm-global helper → Pi package, đúng thứ tự release yêu cầu. Thêm `--project <path>` để chạy doctor sau. |
|
|
@@ -14,9 +14,9 @@ Repo này phải chạy được cho nhiều project/domain khác nhau. Vì vậ
|
|
|
14
14
|
|
|
15
15
|
| Use case | Package source |
|
|
16
16
|
|---|---|
|
|
17
|
-
| Team pinned release | `git:github.com/Vt-mmm/piagent@v1.1.
|
|
17
|
+
| Team pinned release | `git:github.com/Vt-mmm/piagent@v1.1.6` |
|
|
18
18
|
| Personal/sandbox dev | `git:github.com/Vt-mmm/piagent` |
|
|
19
|
-
| Registry distribution | `npm:@piagent/platform@1.1.
|
|
19
|
+
| Registry distribution | `npm:@piagent/platform@1.1.6` |
|
|
20
20
|
| Enterprise npm (fork under another scope) | `npm:@your-scope/platform@x.y.z` |
|
|
21
21
|
| Local platform dev | `/path/to/piagent` |
|
|
22
22
|
|
|
@@ -26,7 +26,7 @@ Latest tiện cho máy cá nhân muốn nhận cập nhật nhanh. Pin tag/commi
|
|
|
26
26
|
|
|
27
27
|
| Channel | Command | Target | Policy |
|
|
28
28
|
|---|---|---|---|
|
|
29
|
-
| `stable` | `bash scripts/install-global.sh --stable` | Helper release tag resolved to commit SHA, currently `v1.1.
|
|
29
|
+
| `stable` | `bash scripts/install-global.sh --stable` | Helper release tag resolved to commit SHA, currently `v1.1.6` | Install the Pi package matching the helper. |
|
|
30
30
|
| `exact` | `bash scripts/install-global.sh --version vX.Y.Z --resolve-tag` | Requested tag resolved to commit SHA | Pi-package-only rollout, rollback, and incident recovery. |
|
|
31
31
|
| `dev` | `bash scripts/install-global.sh --dev` | Moving Git source | Personal/sandbox only; do not commit into project settings. |
|
|
32
32
|
| `local` | `bash scripts/install-global.sh --local` | Current checkout path | Platform development only. |
|
|
@@ -53,7 +53,7 @@ Root `package.json` có `pi` manifest trỏ tới:
|
|
|
53
53
|
Do đó team có thể:
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
|
-
pi install git:github.com/Vt-mmm/piagent@v1.1.
|
|
56
|
+
pi install git:github.com/Vt-mmm/piagent@v1.1.6
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
Không cần biết internal folder `packages/piagent-core`. Source không pin chỉ dành cho personal/sandbox như bảng channel ở trên.
|
|
@@ -65,12 +65,12 @@ Team nên install global package một lần:
|
|
|
65
65
|
```bash
|
|
66
66
|
node --version # >= 22.19.0
|
|
67
67
|
npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.82.0
|
|
68
|
-
npm install -g --ignore-scripts @piagent/platform@1.1.
|
|
68
|
+
npm install -g --ignore-scripts @piagent/platform@1.1.6
|
|
69
69
|
piagent-install --stable --dry-run
|
|
70
70
|
piagent-install --stable
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
`pi install git:github.com/Vt-mmm/piagent@v1.1.
|
|
73
|
+
`pi install git:github.com/Vt-mmm/piagent@v1.1.6` vẫn hợp lệ nếu chỉ cần cài Pi package. Lệnh đó không tự tạo các binary terminal `piagent-*`; muốn có helper global thì dùng `npm install -g --ignore-scripts @piagent/platform@1.1.6`.
|
|
74
74
|
|
|
75
75
|
Support matrix hiện tại: macOS Apple Silicon + Bash và Linux x64 + Bash đã được verify; macOS Intel + Bash và Linux ARM64 + Bash là supported target cần chạy smoke trước rollout; native Windows chưa phải target rollout team; WSL2 experimental. Chi tiết và version runtime nằm trong [release/install policy](release-install-policy.md).
|
|
76
76
|
|
|
@@ -103,7 +103,7 @@ Nếu muốn commit sẵn `.pi/piagent-profile.json` vào repo hoặc bootstrap
|
|
|
103
103
|
```bash
|
|
104
104
|
bash scripts/setup.sh /path/to/project \
|
|
105
105
|
--profile be-readonly-fe \
|
|
106
|
-
--package-source git:github.com/Vt-mmm/piagent@v1.1.
|
|
106
|
+
--package-source git:github.com/Vt-mmm/piagent@v1.1.6 \
|
|
107
107
|
--mcp-preset core \
|
|
108
108
|
--subagents-preset safe
|
|
109
109
|
```
|
|
@@ -18,12 +18,12 @@ Support matrix release hiện tại: macOS Apple Silicon + Bash và Linux x64 +
|
|
|
18
18
|
```bash
|
|
19
19
|
node --version # >= 22.19.0
|
|
20
20
|
npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.82.0
|
|
21
|
-
npm install -g --ignore-scripts @piagent/platform@1.1.
|
|
21
|
+
npm install -g --ignore-scripts @piagent/platform@1.1.6
|
|
22
22
|
piagent-install --stable --dry-run
|
|
23
23
|
piagent-install --stable
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
Khi seed `.pi/settings.json` cho team/repo cần audit lặp lại, giữ package source dạng pinned tag như `git:github.com/Vt-mmm/piagent@v1.1.
|
|
26
|
+
Khi seed `.pi/settings.json` cho team/repo cần audit lặp lại, giữ package source dạng pinned tag như `git:github.com/Vt-mmm/piagent@v1.1.6`. Máy cá nhân có thể dùng `git:github.com/Vt-mmm/piagent` để theo latest.
|
|
27
27
|
|
|
28
28
|
Nếu đang ở source checkout của platform, dùng helper theo channel để preview trước khi đổi:
|
|
29
29
|
|
|
@@ -99,7 +99,7 @@ install package once
|
|
|
99
99
|
|
|
100
100
|
```bash
|
|
101
101
|
npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.82.0
|
|
102
|
-
npm install -g --ignore-scripts @piagent/platform@1.1.
|
|
102
|
+
npm install -g --ignore-scripts @piagent/platform@1.1.6
|
|
103
103
|
piagent-install --stable --dry-run
|
|
104
104
|
piagent-install --stable
|
|
105
105
|
```
|
|
@@ -281,7 +281,7 @@ Không phải daily default. Dùng khi muốn tạo sẵn `.pi` files cho projec
|
|
|
281
281
|
bash /path/to/piagent/scripts/setup.sh /path/to/project \
|
|
282
282
|
--project-only \
|
|
283
283
|
--profile auto \
|
|
284
|
-
--package-source git:github.com/Vt-mmm/piagent@v1.1.
|
|
284
|
+
--package-source git:github.com/Vt-mmm/piagent@v1.1.6 \
|
|
285
285
|
--mcp-preset core \
|
|
286
286
|
--subagents-preset safe
|
|
287
287
|
```
|
|
@@ -854,8 +854,8 @@ Watchdog là optional adversarial reviewer ở cuối turn, không bật mặc
|
|
|
854
854
|
|
|
855
855
|
| Command | Dùng để |
|
|
856
856
|
|---|---|
|
|
857
|
-
| `npm install -g --ignore-scripts @piagent/platform@1.1.
|
|
858
|
-
| `pi install git:github.com/Vt-mmm/piagent@v1.1.
|
|
857
|
+
| `npm install -g --ignore-scripts @piagent/platform@1.1.6` | Cài terminal helper `piagent-*` từ release tag hiện tại. |
|
|
858
|
+
| `pi install git:github.com/Vt-mmm/piagent@v1.1.6` | Install pinned release cho reproducible team setup. |
|
|
859
859
|
| `pi install git:github.com/Vt-mmm/piagent` | Install latest platform package cho máy cá nhân/sandbox. |
|
|
860
860
|
| Cài exact Pi host của release, rồi `npm install -g --ignore-scripts @piagent/platform@X.Y.Z` và `piagent-install --stable` | Full update: đồng bộ host, terminal helper và Pi package. Mỗi release pin một Pi host chính xác; lấy đúng version của release đang cài trong [release/install policy](release-install-policy.md). |
|
|
861
861
|
| Cài exact Pi host ghi trong release cũ, rồi helper `vPREVIOUS` và `piagent-install --stable` | Full rollback; đánh giá lại dependency findings của host cũ trước khi hạ version. |
|
|
@@ -952,7 +952,7 @@ Mở lại Pi session sau khi install.
|
|
|
952
952
|
Cài lại terminal helper đúng release rồi kiểm tra `PATH`:
|
|
953
953
|
|
|
954
954
|
```bash
|
|
955
|
-
npm install -g --ignore-scripts @piagent/platform@1.1.
|
|
955
|
+
npm install -g --ignore-scripts @piagent/platform@1.1.6
|
|
956
956
|
command -v piagent-install
|
|
957
957
|
```
|
|
958
958
|
|
package/docs/project-adapters.md
CHANGED
|
@@ -68,6 +68,22 @@ Path policy fields have distinct meanings:
|
|
|
68
68
|
- `readOnlyPaths`: allow `read`/`grep`/`find`/`ls`, but block `write`/`edit` and shell access.
|
|
69
69
|
- `shellProtectedPaths`: block shell access only. Do not rely on this field to block write/edit; `profile-doctor` and `team-doctor` warn when a path is present only here.
|
|
70
70
|
|
|
71
|
+
Glob trong các field này neo từ project root, nên `backend/**` chỉ khớp backend nằm ở root. Với monorepo, phải liệt kê đúng vị trí thật (`packages/api/**`, `services/*/**`). Không dùng `**/api/**`: nó khớp luôn `packages/web/src/api/` — thư mục HTTP client của frontend — và biến chính phần được phép sửa thành read-only.
|
|
72
|
+
|
|
73
|
+
## Monorepo và workspace
|
|
74
|
+
|
|
75
|
+
Một project = một thư mục mở `pi`. Guard đọc profile tại `<cwd>/.pi/piagent-profile.json` và không đi ngược lên tìm, nên mở `pi` ở thư mục cha chứa nhiều project sẽ bỏ qua profile của từng project con và chạy unprofiled.
|
|
76
|
+
|
|
77
|
+
Detect FE/BE đọc theo thứ tự:
|
|
78
|
+
|
|
79
|
+
1. Tên thư mục ở root: `frontend`, `web`, `client`, `ui` cho FE; `backend`, `server`, `api` cho BE.
|
|
80
|
+
2. Cùng các tên đó ở một cấp dưới `apps/`, `packages/`, `services/`.
|
|
81
|
+
3. Package khai báo trong `workspaces` (package.json, cả dạng mảng lẫn `{ "packages": [...] }`) hoặc `pnpm-workspace.yaml` — mỗi package đọc dependency và config của chính nó, nên `packages/storefront` + `packages/gateway` vẫn ra `fullstack` dù tên không gợi ý gì.
|
|
82
|
+
|
|
83
|
+
Chỉ wildcard ở segment cuối được mở rộng (`packages/*`, `apps/**`); pattern tuyệt đối, pattern có `..`, và pattern loại trừ `!` bị bỏ qua. Số package đọc tối đa 64.
|
|
84
|
+
|
|
85
|
+
Rule nằm ở `packages/piagent-core/extensions/project-shape.js`. Cả `piagent_profile_options` lẫn `scripts/init-project.sh` cùng gọi file này; trước đây mỗi bên giữ một bản riêng và đã lệch nhau.
|
|
86
|
+
|
|
71
87
|
## Built-in adapters
|
|
72
88
|
|
|
73
89
|
| Profile | Dùng cho | Verify mặc định |
|
|
@@ -84,6 +100,11 @@ Path policy fields have distinct meanings:
|
|
|
84
100
|
| `mobile` | React Native/Flutter | npm test / flutter test nếu tool có |
|
|
85
101
|
| `docs` | Docs portal/manual | markdown diff check + test nếu project có |
|
|
86
102
|
|
|
103
|
+
`be-readonly-fe` giữ read-only cho backend ở root (`backend`, `server`, `api`), ở một cấp dưới `apps/` và `packages/` (`api`, `server`, `backend`), toàn bộ `services/*`, và mọi `**/migrations/**`. Profile built-in là điểm khởi đầu, không phải danh sách đầy đủ — sửa `readOnlyPaths` **và** `shellProtectedPaths` trong `.pi/piagent-profile.json` cho đúng repo:
|
|
104
|
+
|
|
105
|
+
- backend đặt tên khác (`apps/gateway`, `packages/core-service`): thêm path vào cả hai field.
|
|
106
|
+
- frontend nằm trong `services/` (ví dụ `services/web`): `services/*/**` sẽ khoá nhầm nó. Thay pattern đó bằng danh sách service backend cụ thể. Triệu chứng là một write bị chặn kèm lý do trỏ tới `readOnlyPaths`, không phải lỗi im lặng.
|
|
107
|
+
|
|
87
108
|
## Runtime profile selection
|
|
88
109
|
|
|
89
110
|
Default UX không bắt buộc chạy bash để set profile. Sau global install, vào project chạy:
|
|
@@ -48,6 +48,7 @@ pi install /path/to/piagent
|
|
|
48
48
|
- `1.1.3`: command mang secret không còn được dùng làm verify evidence, document reader từ chối FIFO thay vì treo, redact secret ngắn, redirect phải về đúng trang được kiểm.
|
|
49
49
|
- `1.1.4`: nhận diện secret dạng CLI option (`--token`, `--password`, `--api-key`) và Authorization header ở mọi vị trí argument.
|
|
50
50
|
- `1.1.5`: thêm `piagent-update` — một lệnh update cả Pi host, terminal helper và Pi package đúng thứ tự.
|
|
51
|
+
- `1.1.6`: `be-readonly-fe` giữ được backend read-only trong monorepo, detect FE/BE đọc cả workspace, và rule detect gộp về một chỗ.
|
|
51
52
|
|
|
52
53
|
## Không publish
|
|
53
54
|
|
|
@@ -10,7 +10,7 @@ This file is the canonical install, update, rollback, and release checklist. Oth
|
|
|
10
10
|
|
|
11
11
|
All supported environments require Node.js `>=22.19.0` and Pi Coding Agent `0.82.0`. The Pi host is installed as a Node CLI; Pi Agent Platform still defines its own release matrix because the terminal helpers and shell policy rely on Bash/POSIX behavior.
|
|
12
12
|
|
|
13
|
-
| Surface | Status for v1.1.
|
|
13
|
+
| Surface | Status for v1.1.6 | Rollout guidance |
|
|
14
14
|
|---|---|---|
|
|
15
15
|
| macOS Apple Silicon (`darwin/arm64`) + Bash | Verified for this release. | Safe default for team rollout after normal project smoke tests. |
|
|
16
16
|
| Linux x64 + Bash | Verified in GitHub Actions. | Safe default for CI/server usage after normal project smoke tests. |
|
|
@@ -33,7 +33,7 @@ These three components are versioned independently. A full install, update, or r
|
|
|
33
33
|
|
|
34
34
|
| Channel | Source shape | Mutability | Use when |
|
|
35
35
|
|---|---|---:|---|
|
|
36
|
-
| `stable` | `git:github.com/Vt-mmm/piagent@<resolved-commit-sha>` from `v1.1.
|
|
36
|
+
| `stable` | `git:github.com/Vt-mmm/piagent@<resolved-commit-sha>` from `v1.1.6` | Fixed after resolution | Default for team rollout. |
|
|
37
37
|
| `exact` | Tag, reviewed commit, or a tag resolved with `--resolve-tag` | Fixed when using a commit SHA | Pi-package-only roll forward, rollback, or reproduction. |
|
|
38
38
|
| `dev` | `git:github.com/Vt-mmm/piagent` | Moving | Personal machine or sandbox only. |
|
|
39
39
|
| `local` | `/path/to/piagent` | Local workspace | Platform development and dry-run validation. |
|
|
@@ -53,7 +53,7 @@ Use this flow when the team needs both terminal commands and the Pi package:
|
|
|
53
53
|
```bash
|
|
54
54
|
node --version # >= 22.19.0
|
|
55
55
|
npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.82.0
|
|
56
|
-
npm install -g --ignore-scripts @piagent/platform@1.1.
|
|
56
|
+
npm install -g --ignore-scripts @piagent/platform@1.1.6
|
|
57
57
|
piagent-install --stable --dry-run
|
|
58
58
|
piagent-install --stable
|
|
59
59
|
```
|
|
@@ -61,8 +61,8 @@ piagent-install --stable
|
|
|
61
61
|
The stable preview and apply output includes:
|
|
62
62
|
|
|
63
63
|
```text
|
|
64
|
-
currentRelease: v1.1.
|
|
65
|
-
tag: v1.1.
|
|
64
|
+
currentRelease: v1.1.6 (helper package version)
|
|
65
|
+
tag: v1.1.6
|
|
66
66
|
resolvedCommit: <40-char-sha>
|
|
67
67
|
source: git:github.com/Vt-mmm/piagent@<40-char-sha>
|
|
68
68
|
```
|
|
@@ -79,7 +79,7 @@ bash scripts/install-global.sh --stable
|
|
|
79
79
|
Use this only when terminal commands are not needed:
|
|
80
80
|
|
|
81
81
|
```bash
|
|
82
|
-
pi install git:github.com/Vt-mmm/piagent@v1.1.
|
|
82
|
+
pi install git:github.com/Vt-mmm/piagent@v1.1.6
|
|
83
83
|
```
|
|
84
84
|
|
|
85
85
|
Direct `pi install` does not create `piagent-*` commands on `PATH`.
|
|
@@ -21,7 +21,7 @@ This document covers the packaged guard, project profiles, capability locks, ins
|
|
|
21
21
|
- The operator, operating-system account, Pi host, and explicitly trusted project repository are trusted to execute code.
|
|
22
22
|
- Project-local Pi resources are loaded only after Pi project trust or an explicit operator override.
|
|
23
23
|
- A human remains available to approve destructive and external-provider actions.
|
|
24
|
-
- Supported release verification covers macOS Apple Silicon + Bash, Linux x64 + Bash, Node.js `>=22.19.0`, and the exact Pi host version declared by the release. macOS Intel + Bash and Linux ARM64 + Bash are supported targets that need local smoke verification before broad rollout. Native Windows is not a team-rollout target for v1.1.
|
|
24
|
+
- Supported release verification covers macOS Apple Silicon + Bash, Linux x64 + Bash, Node.js `>=22.19.0`, and the exact Pi host version declared by the release. macOS Intel + Bash and Linux ARM64 + Bash are supported targets that need local smoke verification before broad rollout. Native Windows is not a team-rollout target for v1.1.6, and WSL2 is experimental/unverified.
|
|
25
25
|
- Registry, GitHub, model-provider, MCP-provider, and Vercel controls are external dependencies. Their account security and platform guarantees are not replaced by this repository.
|
|
26
26
|
|
|
27
27
|
## Threat actors and failure modes
|
package/docs/team-onboarding.md
CHANGED
|
@@ -9,7 +9,7 @@ Một thành viên mới không cần biết local path của maintainer. Luồn
|
|
|
9
9
|
```bash
|
|
10
10
|
node --version # >= 22.19.0
|
|
11
11
|
npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.82.0
|
|
12
|
-
npm install -g --ignore-scripts @piagent/platform@1.1.
|
|
12
|
+
npm install -g --ignore-scripts @piagent/platform@1.1.6
|
|
13
13
|
piagent-install --stable
|
|
14
14
|
cd /path/to/project
|
|
15
15
|
pi
|
|
@@ -42,7 +42,7 @@ Mặc định team dùng stable helper:
|
|
|
42
42
|
```bash
|
|
43
43
|
node --version # >= 22.19.0
|
|
44
44
|
npm install -g --ignore-scripts @earendil-works/pi-coding-agent@0.82.0
|
|
45
|
-
npm install -g --ignore-scripts @piagent/platform@1.1.
|
|
45
|
+
npm install -g --ignore-scripts @piagent/platform@1.1.6
|
|
46
46
|
piagent-install --stable --dry-run
|
|
47
47
|
piagent-install --stable
|
|
48
48
|
```
|
|
@@ -70,7 +70,7 @@ piagent-update --check
|
|
|
70
70
|
piagent-update --project /path/to/project
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
`--check` chỉ báo version, không đụng gì. Lên đúng một bản cụ thể thì `piagent-update --version 1.1.
|
|
73
|
+
`--check` chỉ báo version, không đụng gì. Lên đúng một bản cụ thể thì `piagent-update --version 1.1.6`.
|
|
74
74
|
|
|
75
75
|
Sequence thủ công vẫn dùng được, và bắt buộc theo thứ tự này — `piagent-install` **fail** nếu Pi host lệch bản pin, nó không tự nâng host:
|
|
76
76
|
|
package/docs-site/index.html
CHANGED
|
@@ -798,7 +798,7 @@
|
|
|
798
798
|
<span>Pi Agent Platform</span>
|
|
799
799
|
</a>
|
|
800
800
|
<div class="top-actions">
|
|
801
|
-
<span class="pill"><span class="pill-dot"></span>v1.1.
|
|
801
|
+
<span class="pill"><span class="pill-dot"></span>v1.1.6 docs</span>
|
|
802
802
|
<a class="button social" href="https://github.com/Vt-mmm/piagent" target="_blank" rel="noopener noreferrer" aria-label="Mở GitHub repository">
|
|
803
803
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
804
804
|
<path d="M7 17 17 7M9 7h8v8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
@@ -934,7 +934,7 @@
|
|
|
934
934
|
<article class="step">
|
|
935
935
|
<div>
|
|
936
936
|
<h3>Cài — hai lệnh</h3>
|
|
937
|
-
<p>Cần Node.js 22.19.0 trở lên. Không cần cài Pi Coding Agent trước: <span class="inline-code">piagent-setup</span> tự cài đúng bản <span class="inline-code">0.82.0</span> mà release <span class="inline-code">v1.1.
|
|
937
|
+
<p>Cần Node.js 22.19.0 trở lên. Không cần cài Pi Coding Agent trước: <span class="inline-code">piagent-setup</span> tự cài đúng bản <span class="inline-code">0.82.0</span> mà release <span class="inline-code">v1.1.6</span> đang pin. Chạy nó ngay trong thư mục project — nó cài Pi package, khởi tạo <span class="inline-code">.pi/</span>, cài MCP baseline cùng subagents, rồi chạy doctor. Thêm <span class="inline-code">--no-mcp</span>, <span class="inline-code">--no-subagents</span>, <span class="inline-code">--no-herdr</span> hoặc <span class="inline-code">--global-only</span> để bỏ bớt.</p>
|
|
938
938
|
<div class="table-wrap compact-table">
|
|
939
939
|
<table>
|
|
940
940
|
<thead>
|
package/package.json
CHANGED
|
@@ -795,7 +795,11 @@ function buildCoreIntegrity(root) {
|
|
|
795
795
|
"packages/piagent-core/extensions/guard-shell-analysis.ts",
|
|
796
796
|
"packages/piagent-core/extensions/policy-core.js",
|
|
797
797
|
"packages/piagent-core/extensions/redaction-core.js",
|
|
798
|
-
"packages/piagent-core/extensions/runtime-evidence.js"
|
|
798
|
+
"packages/piagent-core/extensions/runtime-evidence.js",
|
|
799
|
+
// project-shape.js picks the profile a project is offered, and the profile is
|
|
800
|
+
// the document that carries protectedPaths and readOnlyPaths. Steering the
|
|
801
|
+
// recommendation is a way to choose what the guard will end up enforcing.
|
|
802
|
+
"packages/piagent-core/extensions/project-shape.js"
|
|
799
803
|
];
|
|
800
804
|
return files.map((relativePath) => {
|
|
801
805
|
const resolved = resolveRegularFile(root, relativePath, MAX_ARTIFACT_BYTES);
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
redactForStorage,
|
|
40
40
|
redactSensitiveText
|
|
41
41
|
} from "./redaction-core.js";
|
|
42
|
+
import { detectProfileName } from "./project-shape.js";
|
|
42
43
|
import {
|
|
43
44
|
resolveCapabilityProfileDocument,
|
|
44
45
|
verifyCapabilityLock,
|
|
@@ -2636,75 +2637,6 @@ function readAdapterProfiles(extensionDir: string): Array<{ name: string; profil
|
|
|
2636
2637
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
2637
2638
|
}
|
|
2638
2639
|
|
|
2639
|
-
function packageHasAny(cwd: string, pattern: RegExp): boolean {
|
|
2640
|
-
try {
|
|
2641
|
-
return pattern.test(fs.readFileSync(path.join(cwd, "package.json"), "utf8"));
|
|
2642
|
-
} catch {
|
|
2643
|
-
return false;
|
|
2644
|
-
}
|
|
2645
|
-
}
|
|
2646
|
-
|
|
2647
|
-
function detectProfileName(cwd: string, intent?: string): { name: string; reason: string } {
|
|
2648
|
-
const normalizedIntent = intent?.trim().toLowerCase();
|
|
2649
|
-
if (normalizedIntent === "be-readonly-fe") return { name: "be-readonly-fe", reason: "User intent says backend should be read-only while frontend is the write target." };
|
|
2650
|
-
if (normalizedIntent === "frontend-only") return { name: "web-frontend", reason: "User intent says frontend-only." };
|
|
2651
|
-
if (normalizedIntent === "backend-only") return { name: "backend-api", reason: "User intent says backend-only." };
|
|
2652
|
-
if (normalizedIntent === "docs") return { name: "docs", reason: "User intent says docs/docs-only." };
|
|
2653
|
-
|
|
2654
|
-
const hasPackage = fs.existsSync(path.join(cwd, "package.json"));
|
|
2655
|
-
let frontend = false;
|
|
2656
|
-
let backend = false;
|
|
2657
|
-
let data = false;
|
|
2658
|
-
let mobile = false;
|
|
2659
|
-
let infra = false;
|
|
2660
|
-
let docs = false;
|
|
2661
|
-
|
|
2662
|
-
if (fs.existsSync(path.join(cwd, "pubspec.yaml")) || (fs.existsSync(path.join(cwd, "android")) && fs.existsSync(path.join(cwd, "ios")))) mobile = true;
|
|
2663
|
-
if (fs.existsSync(path.join(cwd, "dbt_project.yml")) || fs.existsSync(path.join(cwd, "dvc.yaml")) || fs.existsSync(path.join(cwd, "notebooks")) || fs.existsSync(path.join(cwd, "data"))) data = true;
|
|
2664
|
-
if (fs.existsSync(path.join(cwd, "Dockerfile")) || fs.existsSync(path.join(cwd, "docker-compose.yml")) || fs.existsSync(path.join(cwd, "compose.yml")) || fs.existsSync(path.join(cwd, "compose.yaml")) || fs.existsSync(path.join(cwd, "terraform")) || fs.existsSync(path.join(cwd, "infra")) || fs.existsSync(path.join(cwd, "k8s")) || fs.existsSync(path.join(cwd, "helm"))) infra = true;
|
|
2665
|
-
if (fs.existsSync(path.join(cwd, "docs")) || fs.existsSync(path.join(cwd, "mkdocs.yml")) || fs.existsSync(path.join(cwd, "mint.json")) || fs.existsSync(path.join(cwd, "docusaurus.config.js"))) docs = true;
|
|
2666
|
-
|
|
2667
|
-
if (hasPackage) {
|
|
2668
|
-
if (fs.existsSync(path.join(cwd, "frontend")) || fs.existsSync(path.join(cwd, "apps/web")) || fs.existsSync(path.join(cwd, "apps/frontend"))) frontend = true;
|
|
2669
|
-
if (fs.existsSync(path.join(cwd, "backend")) || fs.existsSync(path.join(cwd, "apps/api")) || fs.existsSync(path.join(cwd, "apps/server"))) backend = true;
|
|
2670
|
-
if (packageHasAny(cwd, /"(next|react|vite|vue|svelte|astro|@angular\/core|remix)"/i)
|
|
2671
|
-
|| fs.existsSync(path.join(cwd, "next.config.js"))
|
|
2672
|
-
|| fs.existsSync(path.join(cwd, "next.config.mjs"))
|
|
2673
|
-
|| fs.existsSync(path.join(cwd, "next.config.ts"))
|
|
2674
|
-
|| fs.existsSync(path.join(cwd, "vite.config.js"))
|
|
2675
|
-
|| fs.existsSync(path.join(cwd, "vite.config.ts"))
|
|
2676
|
-
|| fs.existsSync(path.join(cwd, "src/app"))
|
|
2677
|
-
|| fs.existsSync(path.join(cwd, "pages"))
|
|
2678
|
-
|| fs.existsSync(path.join(cwd, "public"))) frontend = true;
|
|
2679
|
-
if (packageHasAny(cwd, /"(@nestjs|express|fastify|hono|koa|apollo-server|graphql-yoga|prisma|typeorm|sequelize|drizzle-orm)"/i)
|
|
2680
|
-
|| fs.existsSync(path.join(cwd, "nest-cli.json"))
|
|
2681
|
-
|| fs.existsSync(path.join(cwd, "prisma"))
|
|
2682
|
-
|| fs.existsSync(path.join(cwd, "src/server"))
|
|
2683
|
-
|| fs.existsSync(path.join(cwd, "src/api"))) backend = true;
|
|
2684
|
-
}
|
|
2685
|
-
|
|
2686
|
-
if (fs.existsSync(path.join(cwd, "pom.xml")) || fs.existsSync(path.join(cwd, "build.gradle")) || fs.existsSync(path.join(cwd, "build.gradle.kts")) || fs.existsSync(path.join(cwd, "src/main/java")) || fs.existsSync(path.join(cwd, "src/main/kotlin"))) backend = true;
|
|
2687
|
-
|
|
2688
|
-
if (fs.existsSync(path.join(cwd, "pyproject.toml"))) {
|
|
2689
|
-
try {
|
|
2690
|
-
if (/(fastapi|flask|django|litestar|starlite)/i.test(fs.readFileSync(path.join(cwd, "pyproject.toml"), "utf8"))) backend = true;
|
|
2691
|
-
} catch {
|
|
2692
|
-
// ignore unreadable pyproject for recommendation
|
|
2693
|
-
}
|
|
2694
|
-
}
|
|
2695
|
-
|
|
2696
|
-
if (mobile) return { name: "mobile", reason: "Mobile markers found." };
|
|
2697
|
-
if (frontend && backend) return { name: "fullstack", reason: "Frontend and backend markers both found. Pick be-readonly-fe instead if backend must be read-only." };
|
|
2698
|
-
if (frontend) return { name: "web-frontend", reason: "Frontend framework markers found." };
|
|
2699
|
-
if (backend) return { name: "backend-api", reason: "Backend/API markers found." };
|
|
2700
|
-
if (data) return { name: "data", reason: "Data/ETL markers found." };
|
|
2701
|
-
if (fs.existsSync(path.join(cwd, "pyproject.toml"))) return { name: "python", reason: "Python pyproject.toml found." };
|
|
2702
|
-
if (hasPackage && fs.existsSync(path.join(cwd, "tsconfig.json"))) return { name: "node-typescript", reason: "Node TypeScript markers found." };
|
|
2703
|
-
if (infra) return { name: "devops", reason: "Infrastructure markers found." };
|
|
2704
|
-
if (docs) return { name: "docs", reason: "Documentation markers found." };
|
|
2705
|
-
return { name: "generic", reason: "No stronger project markers found." };
|
|
2706
|
-
}
|
|
2707
|
-
|
|
2708
2640
|
function profileDescription(name: string): string {
|
|
2709
2641
|
const descriptions: Record<string, string> = {
|
|
2710
2642
|
generic: "Safe baseline for unknown repos.",
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
// The shape of a repository, in the only terms the profiles care about: is there
|
|
5
|
+
// a frontend, is there a backend, and what else is sitting at the root. Both the
|
|
6
|
+
// runtime recommendation (`piagent_profile_options`) and the shell initialiser
|
|
7
|
+
// (`scripts/init-project.sh`) read it from here. They used to carry separate
|
|
8
|
+
// copies of the same rules, which is how `apps/backend` ended up being invisible
|
|
9
|
+
// to one of them for several releases.
|
|
10
|
+
|
|
11
|
+
// Directories that name a side of the stack outright. These are matched at the
|
|
12
|
+
// repository root and one level under each workspace root, never at arbitrary
|
|
13
|
+
// depth: `src/api` inside a frontend package is an HTTP client, not a backend.
|
|
14
|
+
const FRONTEND_DIR_NAMES = ["frontend", "web", "client", "ui"];
|
|
15
|
+
const BACKEND_DIR_NAMES = ["backend", "server", "api"];
|
|
16
|
+
const WORKSPACE_ROOTS = ["apps", "packages", "services"];
|
|
17
|
+
|
|
18
|
+
const FRONTEND_DEPENDENCIES = /"(next|react|vite|vue|svelte|astro|@angular\/core|remix)"/i;
|
|
19
|
+
const BACKEND_DEPENDENCIES = /"(@nestjs|express|fastify|hono|koa|apollo-server|graphql-yoga|prisma|typeorm|sequelize|drizzle-orm)"/i;
|
|
20
|
+
const PYTHON_BACKEND_FRAMEWORKS = /(fastapi|flask|django|litestar|starlite)/i;
|
|
21
|
+
|
|
22
|
+
const FRONTEND_MARKER_FILES = [
|
|
23
|
+
"next.config.js",
|
|
24
|
+
"next.config.mjs",
|
|
25
|
+
"next.config.ts",
|
|
26
|
+
"vite.config.js",
|
|
27
|
+
"vite.config.ts",
|
|
28
|
+
"src/app",
|
|
29
|
+
"pages",
|
|
30
|
+
"public"
|
|
31
|
+
];
|
|
32
|
+
const NODE_BACKEND_MARKER_FILES = ["nest-cli.json", "prisma", "src/server", "src/api"];
|
|
33
|
+
// Markers that identify a backend on their own, with no manifest to lean on.
|
|
34
|
+
const NATIVE_BACKEND_MARKER_FILES = [
|
|
35
|
+
"pom.xml",
|
|
36
|
+
"build.gradle",
|
|
37
|
+
"build.gradle.kts",
|
|
38
|
+
"src/main/java",
|
|
39
|
+
"src/main/kotlin"
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
// A workspace file can declare any number of packages. Reading a bounded number
|
|
43
|
+
// of them keeps a repository with hundreds of packages from turning profile
|
|
44
|
+
// recommendation into a directory walk.
|
|
45
|
+
const MAX_WORKSPACE_PACKAGES = 64;
|
|
46
|
+
|
|
47
|
+
function exists(dir, relative) {
|
|
48
|
+
try {
|
|
49
|
+
return fs.existsSync(path.join(dir, relative));
|
|
50
|
+
} catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// The stack-naming markers are directories. A file called `api` says nothing
|
|
56
|
+
// about a repository having a backend in it.
|
|
57
|
+
function isDirectory(dir, relative) {
|
|
58
|
+
try {
|
|
59
|
+
return fs.statSync(path.join(dir, relative)).isDirectory();
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function readFile(dir, relative) {
|
|
66
|
+
try {
|
|
67
|
+
return fs.readFileSync(path.join(dir, relative), "utf8");
|
|
68
|
+
} catch {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function readJson(dir, relative) {
|
|
74
|
+
const text = readFile(dir, relative);
|
|
75
|
+
if (text === undefined) return undefined;
|
|
76
|
+
try {
|
|
77
|
+
return JSON.parse(text);
|
|
78
|
+
} catch {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function stripQuotes(value) {
|
|
84
|
+
return value.trim().replace(/^['"]/, "").replace(/['"]$/, "").trim();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// npm and yarn put the globs in package.json, pnpm puts them in its own file.
|
|
88
|
+
// Both are read: a repository is free to carry either, and some carry both.
|
|
89
|
+
function readWorkspacePatterns(cwd) {
|
|
90
|
+
const patterns = [];
|
|
91
|
+
|
|
92
|
+
const declared = readJson(cwd, "package.json")?.workspaces;
|
|
93
|
+
if (Array.isArray(declared)) {
|
|
94
|
+
patterns.push(...declared);
|
|
95
|
+
} else if (Array.isArray(declared?.packages)) {
|
|
96
|
+
patterns.push(...declared.packages);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
patterns.push(...readPnpmWorkspacePatterns(cwd));
|
|
100
|
+
return patterns.filter((pattern) => typeof pattern === "string");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// A three-line YAML reader rather than a YAML dependency: this package ships
|
|
104
|
+
// with no runtime dependencies, and the shape being read is a single list of
|
|
105
|
+
// strings under one key.
|
|
106
|
+
function readPnpmWorkspacePatterns(cwd) {
|
|
107
|
+
const text = readFile(cwd, "pnpm-workspace.yaml") ?? readFile(cwd, "pnpm-workspace.yml");
|
|
108
|
+
if (text === undefined) return [];
|
|
109
|
+
|
|
110
|
+
const patterns = [];
|
|
111
|
+
let insidePackages = false;
|
|
112
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
113
|
+
const line = rawLine.replace(/#.*$/, "");
|
|
114
|
+
if (/^packages\s*:/.test(line)) {
|
|
115
|
+
insidePackages = true;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (!insidePackages) continue;
|
|
119
|
+
|
|
120
|
+
const item = line.match(/^\s+-\s*(.+?)\s*$/);
|
|
121
|
+
if (item) {
|
|
122
|
+
patterns.push(stripQuotes(item[1]));
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
// A blank line stays inside the list; anything else starts the next key.
|
|
126
|
+
if (line.trim().length > 0) break;
|
|
127
|
+
}
|
|
128
|
+
return patterns;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function expandWorkspacePattern(cwd, pattern) {
|
|
132
|
+
const normalized = pattern.trim().replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
133
|
+
// `!` excludes rather than includes, and neither an absolute path nor one that
|
|
134
|
+
// climbs out of the repository is a workspace of this repository.
|
|
135
|
+
if (!normalized || normalized.startsWith("!") || normalized.startsWith("/")) return [];
|
|
136
|
+
|
|
137
|
+
const segments = normalized.split("/").filter((segment) => segment && segment !== ".");
|
|
138
|
+
if (segments.length === 0 || segments.includes("..")) return [];
|
|
139
|
+
|
|
140
|
+
const wildcardAt = segments.findIndex((segment) => segment.includes("*"));
|
|
141
|
+
if (wildcardAt === -1) return [segments.join("/")];
|
|
142
|
+
// Only a trailing wildcard is expanded. `packages/*` and `apps/**` are what
|
|
143
|
+
// workspace files actually declare; anything deeper is left to the directory
|
|
144
|
+
// and dependency markers instead of growing a glob engine in here.
|
|
145
|
+
if (wildcardAt !== segments.length - 1) return [];
|
|
146
|
+
|
|
147
|
+
const base = segments.slice(0, wildcardAt).join("/");
|
|
148
|
+
let entries;
|
|
149
|
+
try {
|
|
150
|
+
entries = fs.readdirSync(base ? path.join(cwd, base) : cwd, { withFileTypes: true });
|
|
151
|
+
} catch {
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
return entries
|
|
155
|
+
.filter((entry) => entry.isDirectory() && !entry.name.startsWith("."))
|
|
156
|
+
.map((entry) => (base ? `${base}/${entry.name}` : entry.name));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function readWorkspacePackageDirs(cwd) {
|
|
160
|
+
const found = [];
|
|
161
|
+
const seen = new Set();
|
|
162
|
+
for (const pattern of readWorkspacePatterns(cwd)) {
|
|
163
|
+
for (const relative of expandWorkspacePattern(cwd, pattern)) {
|
|
164
|
+
if (seen.has(relative)) continue;
|
|
165
|
+
if (!exists(cwd, relative)) continue;
|
|
166
|
+
seen.add(relative);
|
|
167
|
+
found.push(relative);
|
|
168
|
+
if (found.length >= MAX_WORKSPACE_PACKAGES) return found;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return found;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// What one directory says about itself, from its manifest and its own layout.
|
|
175
|
+
// Applied to the repository root and to every declared workspace package, so a
|
|
176
|
+
// pnpm monorepo whose packages are named `web` and `api` is read the same way a
|
|
177
|
+
// single-package repository is.
|
|
178
|
+
function directorySignals(dir) {
|
|
179
|
+
const manifest = readFile(dir, "package.json");
|
|
180
|
+
let frontend = false;
|
|
181
|
+
let backend = false;
|
|
182
|
+
|
|
183
|
+
if (manifest !== undefined) {
|
|
184
|
+
frontend = FRONTEND_DEPENDENCIES.test(manifest);
|
|
185
|
+
backend = BACKEND_DEPENDENCIES.test(manifest);
|
|
186
|
+
// Node layout markers only mean something next to a manifest. `public/` and
|
|
187
|
+
// `pages/` are ordinary directory names in repositories that have nothing to
|
|
188
|
+
// do with a web framework.
|
|
189
|
+
if (!frontend) frontend = FRONTEND_MARKER_FILES.some((marker) => exists(dir, marker));
|
|
190
|
+
if (!backend) backend = NODE_BACKEND_MARKER_FILES.some((marker) => exists(dir, marker));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!backend) backend = NATIVE_BACKEND_MARKER_FILES.some((marker) => exists(dir, marker));
|
|
194
|
+
|
|
195
|
+
const pyproject = readFile(dir, "pyproject.toml");
|
|
196
|
+
if (pyproject !== undefined && PYTHON_BACKEND_FRAMEWORKS.test(pyproject)) backend = true;
|
|
197
|
+
|
|
198
|
+
return { frontend, backend };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function shapeFromDirName(name) {
|
|
202
|
+
const normalized = name.toLowerCase();
|
|
203
|
+
if (FRONTEND_DIR_NAMES.includes(normalized)) return "frontend";
|
|
204
|
+
if (BACKEND_DIR_NAMES.includes(normalized)) return "backend";
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function detectProjectShape(cwd) {
|
|
209
|
+
const hasPackage = exists(cwd, "package.json");
|
|
210
|
+
const shape = {
|
|
211
|
+
hasPackage,
|
|
212
|
+
frontend: false,
|
|
213
|
+
backend: false,
|
|
214
|
+
data: false,
|
|
215
|
+
mobile: false,
|
|
216
|
+
infra: false,
|
|
217
|
+
docs: false,
|
|
218
|
+
workspacePackages: []
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
if (exists(cwd, "pubspec.yaml") || (exists(cwd, "android") && exists(cwd, "ios"))) shape.mobile = true;
|
|
222
|
+
if (exists(cwd, "dbt_project.yml") || exists(cwd, "dvc.yaml") || exists(cwd, "notebooks") || exists(cwd, "data")) {
|
|
223
|
+
shape.data = true;
|
|
224
|
+
}
|
|
225
|
+
if (exists(cwd, "Dockerfile") || exists(cwd, "docker-compose.yml") || exists(cwd, "compose.yml")
|
|
226
|
+
|| exists(cwd, "compose.yaml") || exists(cwd, "terraform") || exists(cwd, "infra")
|
|
227
|
+
|| exists(cwd, "k8s") || exists(cwd, "helm")) {
|
|
228
|
+
shape.infra = true;
|
|
229
|
+
}
|
|
230
|
+
if (exists(cwd, "docs") || exists(cwd, "mkdocs.yml") || exists(cwd, "mint.json") || exists(cwd, "docusaurus.config.js")) {
|
|
231
|
+
shape.docs = true;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Directory names are read whether or not the root carries a package.json.
|
|
235
|
+
// A Django and React repository has `frontend/` and `backend/` and no root
|
|
236
|
+
// manifest at all; gating this on package.json used to hide both sides of it.
|
|
237
|
+
for (const name of [...FRONTEND_DIR_NAMES, ...BACKEND_DIR_NAMES]) {
|
|
238
|
+
const side = shapeFromDirName(name);
|
|
239
|
+
if (isDirectory(cwd, name)) shape[side] = true;
|
|
240
|
+
for (const root of WORKSPACE_ROOTS) {
|
|
241
|
+
if (isDirectory(cwd, `${root}/${name}`)) shape[side] = true;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const rootSignals = directorySignals(cwd);
|
|
246
|
+
if (rootSignals.frontend) shape.frontend = true;
|
|
247
|
+
if (rootSignals.backend) shape.backend = true;
|
|
248
|
+
|
|
249
|
+
shape.workspacePackages = readWorkspacePackageDirs(cwd);
|
|
250
|
+
for (const relative of shape.workspacePackages) {
|
|
251
|
+
const side = shapeFromDirName(path.posix.basename(relative));
|
|
252
|
+
if (side) shape[side] = true;
|
|
253
|
+
|
|
254
|
+
const signals = directorySignals(path.join(cwd, relative));
|
|
255
|
+
if (signals.frontend) shape.frontend = true;
|
|
256
|
+
if (signals.backend) shape.backend = true;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return shape;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function detectProfileName(cwd, intent) {
|
|
263
|
+
const normalizedIntent = typeof intent === "string" ? intent.trim().toLowerCase() : "";
|
|
264
|
+
if (normalizedIntent === "be-readonly-fe") {
|
|
265
|
+
return { name: "be-readonly-fe", reason: "User intent says backend should be read-only while frontend is the write target." };
|
|
266
|
+
}
|
|
267
|
+
if (normalizedIntent === "frontend-only") return { name: "web-frontend", reason: "User intent says frontend-only." };
|
|
268
|
+
if (normalizedIntent === "backend-only") return { name: "backend-api", reason: "User intent says backend-only." };
|
|
269
|
+
if (normalizedIntent === "docs") return { name: "docs", reason: "User intent says docs/docs-only." };
|
|
270
|
+
|
|
271
|
+
const shape = detectProjectShape(cwd);
|
|
272
|
+
|
|
273
|
+
if (shape.mobile) return { name: "mobile", reason: "Mobile markers found." };
|
|
274
|
+
if (shape.frontend && shape.backend) {
|
|
275
|
+
return { name: "fullstack", reason: "Frontend and backend markers both found. Pick be-readonly-fe instead if backend must be read-only." };
|
|
276
|
+
}
|
|
277
|
+
if (shape.frontend) return { name: "web-frontend", reason: "Frontend framework markers found." };
|
|
278
|
+
if (shape.backend) return { name: "backend-api", reason: "Backend/API markers found." };
|
|
279
|
+
if (shape.data) return { name: "data", reason: "Data/ETL markers found." };
|
|
280
|
+
if (exists(cwd, "pyproject.toml")) return { name: "python", reason: "Python pyproject.toml found." };
|
|
281
|
+
if (shape.hasPackage && exists(cwd, "tsconfig.json")) return { name: "node-typescript", reason: "Node TypeScript markers found." };
|
|
282
|
+
if (shape.infra) return { name: "devops", reason: "Infrastructure markers found." };
|
|
283
|
+
if (shape.docs) return { name: "docs", reason: "Documentation markers found." };
|
|
284
|
+
return { name: "generic", reason: "No stronger project markers found." };
|
|
285
|
+
}
|
package/scripts/init-project.sh
CHANGED
|
@@ -102,97 +102,20 @@ fi
|
|
|
102
102
|
|
|
103
103
|
PROJECT_PATH="$(cd "$PROJECT_PATH" && pwd)"
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
[[ -f "$project/package.json" ]] && grep -Eiq "$pattern" "$project/package.json"
|
|
109
|
-
}
|
|
110
|
-
|
|
105
|
+
# Profile detection lives in packages/piagent-core/extensions/project-shape.js so
|
|
106
|
+
# that this script and the runtime `piagent_profile_options` tool recommend the
|
|
107
|
+
# same profile. They used to hold separate copies of the rules and drifted apart.
|
|
111
108
|
detect_profile() {
|
|
112
109
|
local project="$1"
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
[[ -f "$project/package.json" ]] && has_package=true
|
|
123
|
-
|
|
124
|
-
if [[ -f "$project/pubspec.yaml" ]] || { [[ -d "$project/android" ]] && [[ -d "$project/ios" ]]; }; then
|
|
125
|
-
mobile=true
|
|
126
|
-
fi
|
|
127
|
-
|
|
128
|
-
if [[ -f "$project/dbt_project.yml" ]] || [[ -f "$project/dvc.yaml" ]] || [[ -d "$project/notebooks" ]] || [[ -d "$project/data" ]]; then
|
|
129
|
-
data=true
|
|
130
|
-
fi
|
|
131
|
-
|
|
132
|
-
if [[ -f "$project/Dockerfile" ]] || [[ -f "$project/docker-compose.yml" ]] || [[ -f "$project/compose.yml" ]] || [[ -f "$project/compose.yaml" ]] || [[ -d "$project/terraform" ]] || [[ -d "$project/infra" ]] || [[ -d "$project/k8s" ]] || [[ -d "$project/helm" ]]; then
|
|
133
|
-
infra=true
|
|
134
|
-
devops=true
|
|
135
|
-
elif [[ -d "$project/.github/workflows" ]]; then
|
|
136
|
-
devops=true
|
|
137
|
-
fi
|
|
138
|
-
|
|
139
|
-
if [[ -d "$project/docs" ]] || [[ -f "$project/mkdocs.yml" ]] || [[ -f "$project/mint.json" ]] || [[ -f "$project/docusaurus.config.js" ]]; then
|
|
140
|
-
docs=true
|
|
141
|
-
fi
|
|
142
|
-
|
|
143
|
-
if [[ "$has_package" == true ]]; then
|
|
144
|
-
if [[ -d "$project/frontend" ]] || [[ -d "$project/apps/web" ]] || [[ -d "$project/apps/frontend" ]]; then
|
|
145
|
-
frontend=true
|
|
146
|
-
fi
|
|
147
|
-
|
|
148
|
-
if [[ -d "$project/backend" ]] || [[ -d "$project/apps/api" ]] || [[ -d "$project/apps/server" ]]; then
|
|
149
|
-
backend=true
|
|
150
|
-
fi
|
|
151
|
-
|
|
152
|
-
if package_has_any "$project" '"(next|react|vite|vue|svelte|astro|@angular/core|remix)"' \
|
|
153
|
-
|| [[ -f "$project/next.config.js" ]] || [[ -f "$project/next.config.mjs" ]] || [[ -f "$project/next.config.ts" ]] \
|
|
154
|
-
|| [[ -f "$project/vite.config.js" ]] || [[ -f "$project/vite.config.ts" ]] \
|
|
155
|
-
|| [[ -d "$project/src/app" ]] || [[ -d "$project/pages" ]] || [[ -d "$project/public" ]]; then
|
|
156
|
-
frontend=true
|
|
157
|
-
fi
|
|
158
|
-
|
|
159
|
-
if package_has_any "$project" '"(@nestjs|express|fastify|hono|koa|apollo-server|graphql-yoga|prisma|typeorm|sequelize|drizzle-orm)"' \
|
|
160
|
-
|| [[ -f "$project/nest-cli.json" ]] || [[ -d "$project/prisma" ]] || [[ -d "$project/src/server" ]] || [[ -d "$project/src/api" ]]; then
|
|
161
|
-
backend=true
|
|
162
|
-
fi
|
|
163
|
-
fi
|
|
164
|
-
|
|
165
|
-
if [[ -f "$project/pom.xml" ]] || [[ -f "$project/build.gradle" ]] || [[ -f "$project/build.gradle.kts" ]] || [[ -d "$project/src/main/java" ]] || [[ -d "$project/src/main/kotlin" ]]; then
|
|
166
|
-
backend=true
|
|
167
|
-
fi
|
|
168
|
-
|
|
169
|
-
if [[ -f "$project/pyproject.toml" ]] && grep -Eiq "(fastapi|flask|django|litestar|starlite)" "$project/pyproject.toml"; then
|
|
170
|
-
backend=true
|
|
171
|
-
fi
|
|
172
|
-
|
|
173
|
-
if [[ "$mobile" == true ]]; then
|
|
174
|
-
printf '%s\n' "mobile"
|
|
175
|
-
elif [[ "$frontend" == true && "$backend" == true ]]; then
|
|
176
|
-
printf '%s\n' "fullstack"
|
|
177
|
-
elif [[ "$frontend" == true ]]; then
|
|
178
|
-
printf '%s\n' "web-frontend"
|
|
179
|
-
elif [[ "$backend" == true ]]; then
|
|
180
|
-
printf '%s\n' "backend-api"
|
|
181
|
-
elif [[ "$data" == true ]]; then
|
|
182
|
-
printf '%s\n' "data"
|
|
183
|
-
elif [[ -f "$project/pyproject.toml" ]]; then
|
|
184
|
-
printf '%s\n' "python"
|
|
185
|
-
elif [[ "$has_package" == true && -f "$project/tsconfig.json" ]]; then
|
|
186
|
-
printf '%s\n' "node-typescript"
|
|
187
|
-
elif [[ "$infra" == true ]]; then
|
|
188
|
-
printf '%s\n' "devops"
|
|
189
|
-
elif [[ "$docs" == true ]]; then
|
|
190
|
-
printf '%s\n' "docs"
|
|
191
|
-
elif [[ "$devops" == true ]]; then
|
|
192
|
-
printf '%s\n' "devops"
|
|
193
|
-
else
|
|
194
|
-
printf '%s\n' "generic"
|
|
195
|
-
fi
|
|
110
|
+
node --input-type=module - "$PLATFORM_ROOT" "$project" <<'NODE'
|
|
111
|
+
import path from "node:path";
|
|
112
|
+
import { pathToFileURL } from "node:url";
|
|
113
|
+
|
|
114
|
+
const [platformRoot, project] = process.argv.slice(2);
|
|
115
|
+
const modulePath = path.join(platformRoot, "packages/piagent-core/extensions/project-shape.js");
|
|
116
|
+
const { detectProfileName } = await import(pathToFileURL(modulePath).href);
|
|
117
|
+
process.stdout.write(`${detectProfileName(project).name}\n`);
|
|
118
|
+
NODE
|
|
196
119
|
}
|
|
197
120
|
|
|
198
121
|
resolve_profile_path() {
|
|
@@ -15,7 +15,7 @@ Package source examples:
|
|
|
15
15
|
# Installs the stable tag after resolving it to a commit SHA.
|
|
16
16
|
|
|
17
17
|
# Pin a specific release:
|
|
18
|
-
scripts/install-global.sh --version v1.1.
|
|
18
|
+
scripts/install-global.sh --version v1.1.6 --resolve-tag
|
|
19
19
|
|
|
20
20
|
# Preview the planned install/update without changing user config:
|
|
21
21
|
scripts/install-global.sh --stable --dry-run
|
|
@@ -127,7 +127,7 @@ detect_runtime_surface() {
|
|
|
127
127
|
printf 'native-windows/%s (not release-gated; terminal helpers require Bash semantics)' "$arch_name"
|
|
128
128
|
;;
|
|
129
129
|
*)
|
|
130
|
-
printf '%s/%s (outside v1.1.
|
|
130
|
+
printf '%s/%s (outside v1.1.6 release matrix)' "$os_name" "$arch_name"
|
|
131
131
|
;;
|
|
132
132
|
esac
|
|
133
133
|
}
|
|
@@ -135,7 +135,7 @@ detect_runtime_surface() {
|
|
|
135
135
|
warn_runtime_surface_if_needed() {
|
|
136
136
|
local surface="$1"
|
|
137
137
|
case "$surface" in
|
|
138
|
-
*"supported target"*|*"experimental"*|*"not release-gated"*|*"outside v1.1.
|
|
138
|
+
*"supported target"*|*"experimental"*|*"not release-gated"*|*"outside v1.1.6 release matrix"*)
|
|
139
139
|
echo "WARN: runtime surface is $surface." >&2
|
|
140
140
|
echo "WARN: For team rollout, run piagent-doctor plus the project smoke/verify suite on this machine before relying on it." >&2
|
|
141
141
|
;;
|
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.1.
|
|
140
|
+
note: "This runtime is outside the documented v1.1.6 support matrix."
|
|
141
141
|
};
|
|
142
142
|
}
|
|
143
143
|
|