@aefree/pi-plastic 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +109 -0
  2. package/CONTRIBUTING.md +88 -0
  3. package/LICENSE +21 -0
  4. package/README.md +181 -0
  5. package/SECURITY.md +31 -0
  6. package/evals/tool-loading/README.md +38 -0
  7. package/evals/tool-loading/RESULTS.md +57 -0
  8. package/evals/tool-loading/baseline.json +34 -0
  9. package/evals/tool-loading/cases.json +70 -0
  10. package/evals/tool-loading/config.json +19 -0
  11. package/evals/tool-loading/mutation-guard.ts +30 -0
  12. package/evals/tool-loading/provider-capture.ts +22 -0
  13. package/evals/tool-loading/results/.gitignore +3 -0
  14. package/evals/tool-loading/run-eval.ts +372 -0
  15. package/extensions/bash-cm-diff-guard.ts +49 -0
  16. package/extensions/bash-cm-merge-guard.ts +53 -0
  17. package/extensions/plastic-branch-status.ts +285 -0
  18. package/extensions/repository-search-provider.ts +66 -0
  19. package/extensions/shared/bash-command-guards.ts +67 -0
  20. package/index.ts +777 -0
  21. package/package.json +76 -0
  22. package/skills/_shared/references/conventional-commits.md +198 -0
  23. package/skills/using-plastic/SKILL.md +50 -0
  24. package/skills/using-plastic/references/branch-management.md +78 -0
  25. package/skills/using-plastic/references/changeset-operations.md +96 -0
  26. package/skills/using-plastic/references/code-review-creation.md +30 -0
  27. package/skills/using-plastic/references/integration.md +47 -0
  28. package/skills/using-plastic/references/quick-reference.md +32 -0
  29. package/skills/using-plastic/references/resources.md +7 -0
  30. package/skills/using-plastic/references/reviewing-changes.md +71 -0
  31. package/skills/using-plastic/references/shelving.md +25 -0
  32. package/skills/using-plastic/references/troubleshooting.md +115 -0
  33. package/skills/using-plastic/references/workspaces.md +33 -0
  34. package/src/pi-tool-compat.ts +56 -0
  35. package/src/plastic-core.ts +4204 -0
  36. package/src/plastic-tool-loading.ts +234 -0
  37. package/src/plastic-workspace.ts +176 -0
  38. package/src/repository-search-provider.ts +64 -0
  39. package/tests/bash-cm-diff-guard-validation.ts +55 -0
  40. package/tests/fixtures/plastic.selector +3 -0
  41. package/tests/fixtures/status-compact.txt +1 -0
  42. package/tests/fixtures/status-normal.txt +1 -0
  43. package/tests/packed-copy-owner-conflict.test.ts +28 -0
  44. package/tests/pi-tool-harness.ts +116 -0
  45. package/tests/plastic-branch-create-live.test.ts +78 -0
  46. package/tests/plastic-branch-query.test.ts +26 -0
  47. package/tests/plastic-branch-status.test.ts +241 -0
  48. package/tests/plastic-checkin-path-resolution.ts +219 -0
  49. package/tests/plastic-constrained-sampling.test.ts +77 -0
  50. package/tests/plastic-diff-workflow.test.ts +86 -0
  51. package/tests/plastic-dynamic-tool-loading.test.ts +224 -0
  52. package/tests/plastic-extension-registration.test.ts +34 -0
  53. package/tests/plastic-live-smoke.test.ts +37 -0
  54. package/tests/plastic-mutation-execution.test.ts +61 -0
  55. package/tests/plastic-patch-command.test.ts +118 -0
  56. package/tests/plastic-process-lifecycle.test.ts +148 -0
  57. package/tests/plastic-tool-rendering.test.ts +16 -0
  58. package/tests/plastic-tool-validation.ts +153 -0
  59. package/tests/plastic-workspace.test.ts +62 -0
  60. package/tests/repository-search-provider-registration.test.ts +37 -0
  61. package/tests/repository-search-provider.test.ts +27 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,109 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project follows semantic versioning for public package releases.
7
+
8
+ ## [0.4.0] - 2026-08-01
9
+
10
+ ### Added
11
+
12
+ - Added bounded portable `plastic_diffFile`/`plastic_diffRevisions` output metadata, explicit binary results, temporary historical-content cleanup, Unity-YAML-as-text coverage, revision validation, and Windows GNU `diff` regression coverage.
13
+
14
+ - Added session-scoped generic repository-search policy. The policy safely discovers bounded `ignore.conf`/`cloaked.conf` chains.
15
+ - Added actual package-root/version owner discovery at the extension boundary and packed-copy owner-conflict coverage.
16
+ - Added direct mutation execution coverage proving one exact `cm` process attempt with no approval context and no implicit retry.
17
+
18
+ - Added a composable `plastic-branch` footer status with nearest-workspace discovery, selector fallback, bounded `cm` confirmation, and debounced same-workspace refresh.
19
+ - Added credential-free workspace, selector/status parser, footer composition, and lifecycle cleanup coverage using sanitized fixtures.
20
+ - Added an opt-in, local-only branch-creation test for contributor-authenticated disposable Plastic repositories.
21
+
22
+ ### Changed
23
+
24
+ - Made `@aefree/pi-repo-search` an optional peer: Plastic ignore/cloak policy registration is enabled when the package is installed, while core Plastic tools and skills load normally without it.
25
+
26
+ - Made `plastic_diffFile(path=...)` default to the workspace Plastic base, route changed-file questions to `plastic_status`, and document supported advanced revision forms without agent-authored `cm cat` temporary-file recipes.
27
+
28
+ - Updated the Pi development baseline to 0.83.0.
29
+
30
+ - Removed the `vcs.plastic` workflow provider, its marker/readiness/preflight/guidance integration, and the unused optional `@aefree/pi-workflow` dependency. Repository-search policy remains independently registered and Plastic tools retain operation-specific validation.
31
+ - Stopped registering or dynamically advertising `plastic_workspaceCreate` until workspace creation has a paired, safe cleanup capability.
32
+ - Plastic repository policy outputs now declare the canonical marker-owned `policyOwnedRoot` boundary used by repo-search physical ignore-file containment.
33
+ - Canonical contract packages are normal semver dependencies rather than bundled local links, preventing sibling/`node_modules` path leakage in packed tarballs.
34
+ - Shared Plastic status branch parsing with `plastic_currentBranch` while preserving its `cm`-authoritative source policy.
35
+ - Made `plastic_branchCreate` support an explicit parent branch independent of the loaded workspace branch, default relative names to the current branch when omitted, and reject accidental top-level branches unless `allowRootBranch=true` is explicitly supplied.
36
+ - Removed package-owned token and UI-confirmation approval from Plastic mutations. Direct calls now proceed through the existing command, workspace-readiness, exact-target, path-containment, and process-safety checks to one `cm` process attempt without implicit retry.
37
+
38
+ ## [0.3.0] - 2026-07-25
39
+
40
+ ### Added
41
+
42
+ - Added `plastic_tool_search`, a bounded package-owned dynamic loader with explicit Plastic aliases, tags, and selected-tool safety guidance.
43
+ - Added balanced, loader-only, and all-active initial tool-loading modes through `PI_PLASTIC_TOOL_LOADING_MODE`, plus active-branch restoration of valid historical loader additions.
44
+ - Added harness coverage for dynamic tool composition, search, additive activation, restoration, and deferred prompt-metadata behavior.
45
+ - Added a package-local dynamic tool-loading behavioral eval with fresh Pi JSON subprocesses, bounded configured cases, implicit discovery coverage, exact smallest-sufficient activation checks, preflight-only mutation guarding, sandbox attestations, provider-schema capture hygiene, and ignored sanitized result summaries.
46
+ - Added an opt-in, read-only live smoke test for dedicated Plastic sandbox workspaces.
47
+ - Added a compatibility audit that prevents JSON-schema constrained sampling from being enabled on schemas that OpenAI strict tools would reject.
48
+ - Added cross-platform Windows, macOS, and Linux validation plus a manually dispatched, tag-verified GitHub Release workflow.
49
+ - Added Dependabot configuration, contribution and security policies, and repository-wide text/ignore conventions.
50
+
51
+ ### Changed
52
+
53
+ - Declared Pi 0.82 and TypeBox development dependencies so the expanded test suite installs reproducibly while keeping runtime peers optional.
54
+ - Declared the supported Node.js engine, expanded package metadata, added an explicit tarball allowlist and reproducible development lockfile, and documented pinned HTTPS and SSH installs.
55
+
56
+ ### Fixed
57
+
58
+ - Allow eval validation on compatible Pi 0.82 patch releases instead of rejecting every version except 0.82.0.
59
+ - Resolve relative Pi extension provenance paths against their canonical extension base directory so standard relative `--extension` loading retains dynamic activation.
60
+ - Fail safe when canonical Pi `sourceInfo` provenance or effective loader ownership is unavailable: preserve the active set exactly and never activate or defer an unproven `plastic_*` name.
61
+ - Fixed full-path branch existence checks by querying Plastic's leaf-name field and validating the returned full branch path.
62
+
63
+ ## [0.2.4] - 2026-07-24
64
+
65
+ ### Changed
66
+
67
+ - Marked Pi-bundled core dependencies as optional peers so Pi git installs do not create redundant per-package `node_modules` directories.
68
+
69
+ ## [0.2.3] - 2026-07-10
70
+
71
+ ### Changed
72
+
73
+ - Migrated Pi extension imports and peer dependencies to the `@earendil-works` package scope, and removed the unused `pi-ai` peer dependency.
74
+
75
+ ### Fixed
76
+
77
+ - Normalize Windows-style absolute and relative checkin paths with Windows path semantics even when validation runs on another operating system.
78
+
79
+ ## [0.2.2] - 2026-07-09
80
+
81
+ ### Fixed
82
+
83
+ - Preserve case distinctions on case-sensitive Darwin/APFS volumes while still matching paths case-insensitively on detected case-insensitive volumes and Windows, without inferring a mounted volume's policy from its parent filesystem.
84
+ - Make spawned Plastic command cancellation escalate based on terminal process settlement, with prompt listener and timer cleanup.
85
+ - Report actionable diagnostics when the Plastic or Git executable cannot be launched.
86
+
87
+ ### Added
88
+
89
+ - Support `PI_PLASTIC_CM_EXECUTABLE` and `PI_PLASTIC_GIT_EXECUTABLE` executable-path overrides.
90
+ - Add macOS CI coverage for tests and package validation.
91
+
92
+ ## [0.2.1] - 2026-06-27
93
+
94
+ ### Changed
95
+
96
+ - Changed `plastic_mergeToBranch` so an omitted target resolves to the source branch's Plastic parent branch instead of assuming `/dev`.
97
+
98
+ ## [0.2.0] - 2026-06-27
99
+
100
+ ### Added
101
+
102
+ - Added `plastic_mergeToBranch`, a safe closeout helper that switches to a target branch, optionally updates, merges a source branch non-interactively, verifies merge state, and checks in the merge result.
103
+ - Documented the closeout merge helper in the README and `using-plastic` skill.
104
+
105
+ ## [0.1.0] - 2026-04-27
106
+
107
+ ### Added
108
+
109
+ - Initial Plastic SCM / Unity Version Control package with status, branch, merge, checkin, shelveset, patch, diff-safe, and code-review tools.
@@ -0,0 +1,88 @@
1
+ # Contributing
2
+
3
+ Thanks for improving Pi Plastic.
4
+
5
+ ## Prerequisites
6
+
7
+ - Node.js 22.19.0 or newer
8
+ - npm
9
+ - Plastic SCM / Unity Version Control CLI for opt-in live validation
10
+ - Git for the text-only diff tools
11
+
12
+ ## Setup
13
+
14
+ ```bash
15
+ npm ci --ignore-scripts --no-audit --no-fund
16
+ npm test
17
+ ```
18
+
19
+ The default suite is credential-free. Keep it that way. Tests that require Plastic access must remain explicitly opt-in and target a dedicated disposable workspace.
20
+
21
+ ## Making changes
22
+
23
+ - Keep public tool names and output shapes stable unless the change intentionally documents a breaking release.
24
+ - Preserve built-in and foreign-extension tools when changing active-tool composition.
25
+ - Do not weaken confirmation, preflight, path, process, or mutation safeguards.
26
+ - Add focused regression coverage for behavior changes.
27
+ - Update README documentation when user-facing behavior changes.
28
+ - Keep unreleased user-visible changes under `## Unreleased` in `CHANGELOG.md` during ordinary development.
29
+ - Do not bump the package version for every implementation commit. Convert the accumulated Unreleased section into one dated SemVer release only when preparing a release.
30
+
31
+ ## Validation
32
+
33
+ Run:
34
+
35
+ ```bash
36
+ npm test
37
+ npm run eval:tool-loading -- --dry-run
38
+ npm pack --dry-run
39
+ ```
40
+
41
+ Review the tarball list for machine-specific paths, credentials, raw provider payloads, generated results, and unrelated files.
42
+
43
+ Plastic live tests are intentionally local-only. This project has no shared Plastic Cloud account or CI credentials, and contributors must authenticate `cm` with their own account and use a repository they are authorized to modify. Never add Plastic credentials, tokens, cloud organization details, or private repository data to tests, fixtures, logs, pull requests, or GitHub Actions.
44
+
45
+ For an opt-in read-only Plastic smoke test:
46
+
47
+ ```bash
48
+ PI_PLASTIC_TEST_WORKSPACE=/absolute/path/to/dedicated/sandbox npm run test:live
49
+ ```
50
+
51
+ For branch-creation behavior, use the opt-in mutation test only against a clean, dedicated disposable repository:
52
+
53
+ ```bash
54
+ PI_PLASTIC_TEST_WORKSPACE=/absolute/path/to/dedicated/sandbox \
55
+ PI_PLASTIC_ALLOW_MUTATION_TESTS=true \
56
+ npm run test:live:branch-create
57
+ ```
58
+
59
+ The mutation test creates uniquely named hierarchical branches, verifies explicit-parent behavior and the top-level guard, does not switch the workspace, and removes temporary branches in cleanup.
60
+
61
+ ### Behavior completion gate
62
+
63
+ When a change modifies how a `plastic_*` tool invokes or interprets real Plastic behavior:
64
+
65
+ 1. Add or update credential-free regression coverage for policy, parsing, and command construction.
66
+ 2. Locally run the narrowest relevant opt-in live test in a clean disposable Plastic repository using the contributor's own authenticated account.
67
+ 3. Verify the workspace state and temporary-resource cleanup afterward.
68
+ 4. Report the exact live test and that an authenticated disposable repository was used, without exposing account, organization, server, credential, or private repository details.
69
+
70
+ GitHub Actions is not expected to run Plastic live tests. If local authenticated validation is unavailable, describe the work as implemented with live validation pending; do not claim the behavior is done or verified. Documentation-only and purely internal changes may mark live validation as not applicable with a reason.
71
+
72
+ Follow `evals/tool-loading/README.md` before any live model evaluation. Never use an ordinary production workspace for mutation testing.
73
+
74
+ ## Pull requests
75
+
76
+ Keep pull requests focused. Describe:
77
+
78
+ - the behavior changed;
79
+ - safety or compatibility considerations;
80
+ - tests and environments actually run;
81
+ - checks intentionally skipped;
82
+ - relevant before/after metadata or latency measurements for loader changes.
83
+
84
+ Do not include secrets, account data, private repository content, or raw provider captures.
85
+
86
+ ## Security reports
87
+
88
+ Follow [SECURITY.md](SECURITY.md) instead of opening a public issue for vulnerabilities.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aaron Freedman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,181 @@
1
+ # Pi Plastic
2
+
3
+ Pi tools, footer status, and skill guidance for Plastic SCM / Unity Version Control workflows.
4
+
5
+ ## Plastic branch footer status
6
+
7
+ When Pi starts inside a Plastic workspace, this package adds a themed `Plastic <branch>` status to Pi's built-in footer. It discovers the nearest enclosing `.plastic/plastic.workspace`, reads the local selector as a fast credential-free fallback, and confirms the branch with a bounded `cm status` call. Selector changes and successful same-workspace `plastic_*` tools refresh the status; sibling workspaces are ignored.
8
+
9
+ The extension owns only the `plastic-branch` status key. It does not replace Pi's footer or suppress Pi's Git branch display, so Git and Plastic information can appear together in nested workspaces. If the Plastic marker exists but neither the selector nor `cm` yields a branch, the footer shows `Plastic branch unavailable`. Non-Plastic directories show no Plastic status.
10
+
11
+ ## Repository-search policy
12
+
13
+ When `@aefree/pi-repo-search` is also installed, Pi session startup registers the independent `plastic.ignore-files` repository-search policy. The marker-based policy discovers readable `ignore.conf` and `cloaked.conf` only from the nearest Plastic workspace to each requested root, then adds them as ripgrep ignore files without replacing native ripgrep/Git-ignore behavior. The integration is optional: without `pi-repo-search`, all Plastic tools and skills still load and only this policy registration is omitted. It does not register workflow guidance or perform Plastic CLI readiness checks; owning `plastic_*` tools validate their own workspace and CLI requirements.
14
+
15
+
16
+ ## Mutation execution
17
+
18
+ Directly invoked mutating `plastic_*` tools do not require approval tokens or package-owned UI confirmation. After the tool's existing argument, command, workspace-readiness, exact-target, and path-containment checks pass, each command attempt proceeds to its intended `cm` spawn. The process layer makes one attempt and does not retry implicitly; inspect Plastic status before manually retrying an ambiguous side-effecting failure.
19
+
20
+ Removing the approval layer does not relax Plastic safety guards. Command allowlists, exact mutation targets, workspace/path containment, non-interactive process selection, blocked `cm diff`, safe merge flags, and operation-specific preflight behavior remain authoritative. Compound tools may intentionally execute multiple validated command steps.
21
+
22
+ ## Tools
23
+
24
+ - `plastic_tool_search` (dynamic capability search and loader)
25
+ - `plastic_status`
26
+ - `plastic_update`
27
+ - `plastic_add`
28
+ - `plastic_checkin`
29
+ - `plastic_undo`
30
+ - `plastic_resolveDeleteChangeConflict`
31
+ - `plastic_diff`
32
+ - `plastic_patch`
33
+ - `plastic_diffRevisions`
34
+ - `plastic_diffFile`
35
+ - `plastic_branchCreate`
36
+ - `plastic_switchBranch`
37
+ - `plastic_merge`
38
+ - `plastic_mergeToBranch`
39
+ - `plastic_finalizeMerge`
40
+ - `plastic_currentBranch`
41
+ - `plastic_branchList`
42
+ - `plastic_branchExists`
43
+ - `plastic_branchDelete`
44
+ - `plastic_shelvesetCreate`
45
+ - `plastic_shelvesetApply`
46
+ - `plastic_shelvesetDelete`
47
+ - `plastic_shelvesetList`
48
+ - `plastic_codeReviewCreate`
49
+ - `plastic_codeReviewUpdate`
50
+ - `plastic_codeReviewDelete`
51
+ - `plastic_codeReviewFind`
52
+ - `plastic_workspaceList`
53
+
54
+ ## Dynamic tool loading
55
+
56
+ The package exposes 28 public `plastic_*` tools. `plastic_workspaceCreate` is intentionally not registered or discoverable until the package provides a paired, safe workspace-cleanup capability. `plastic_tool_search` is a package-owned loader that searches the explicit Plastic capability catalog, reports bounded matches and safety guidance, and additively enables selected tools for the next model request.
57
+
58
+ The default **balanced** session set keeps `plastic_tool_search`, `plastic_status`, and `plastic_currentBranch` active. The other Plastic tools remain registered but inactive until selected; built-in and other-extension tools are not removed. Previous loader additions on the active session branch are restored on startup, resume, fork, and reload.
59
+
60
+ For controlled comparisons, set `PI_PLASTIC_TOOL_LOADING_MODE` before starting Pi:
61
+
62
+ ```bash
63
+ PI_PLASTIC_TOOL_LOADING_MODE=balanced # default production candidate
64
+ PI_PLASTIC_TOOL_LOADING_MODE=loader-only # maximum initial schema reduction
65
+ PI_PLASTIC_TOOL_LOADING_MODE=all-active # all 28 currently exposed tools; loader omitted
66
+ ```
67
+
68
+ Pi 0.82 and newer use canonical `sourceInfo` provenance to identify this package's effective tools before deferring, restoring, or activating them. If canonical provenance or ownership of the effective loader cannot be proven, `pi-plastic` fails safe: it preserves the complete current active set exactly, does not defer, remove, or activate any `plastic_*` name, and an effective package loader can only report known tools that are already active rather than activating inactive names. On sourceInfo-capable Pi instances, providers without native deferred definitions still receive the complete current active set after a loader call. Reload or restart Pi after source edits; source files are not watched automatically.
69
+
70
+ ## Safety behavior
71
+
72
+ - `plastic_branchCreate` supports an explicit parent branch independent of the loaded workspace branch, defaults relative names to the current branch when no parent is supplied, and rejects top-level paths unless `allowRootBranch=true` is explicit.
73
+ - `plastic_diff` remains a disabled alias by design; use `plastic_diffFile` or `plastic_diffRevisions` for text-only file diffs.
74
+ - `plastic_patch` generates review patches with `cm patch`, including `clean` and `integration` filters for branch review workflows. It does not expose patch apply.
75
+ - Bash safety rails block `cm diff` and unsafe interactive `cm merge --merge` usage.
76
+ - Merge tooling surfaces Plastic `FILE_CONFLICT` records and merge-state metadata from `cm status`.
77
+ - `plastic_mergeToBranch` performs the common safe closeout flow: resolve the source branch's parent as the default target, switch to the target branch, optionally update, merge a source branch non-interactively, verify merge state, and check in the merge result.
78
+ - `plastic_finalizeMerge` supports reviewed/manual-resolution flows where Plastic still needs merge metadata finalized before checkin.
79
+
80
+ ## Patch generation examples
81
+
82
+ ```text
83
+ plastic_patch(source="<branch-spec>", integration=true)
84
+ plastic_patch(source="<branch-spec>", clean=true, integration=true, output="<patch-file>")
85
+ plastic_patch(source="<left-spec>", destination="<right-spec>")
86
+ plastic_patch(source="<branch-spec>", toolPath="<path-to-diff-tool>")
87
+ ```
88
+
89
+ If `output` is omitted, Plastic prints patch content to stdout. If `output` is provided, Plastic writes a new patch file and refuses to overwrite an existing file. Inspect patches before sharing them because they can contain source code, binary content, local paths, or secrets that were present in the changed files.
90
+
91
+ ## Included skill
92
+
93
+ - `using-plastic` - PlasticSCM branch, workspace, merge, shelveset, checkin, and code-review workflow guidance
94
+
95
+ ## Install
96
+
97
+ Install the stable GitHub release over HTTPS:
98
+
99
+ ```bash
100
+ pi install git:github.com/aefreedman/pi-plastic@v0.4.0
101
+ ```
102
+
103
+ Equivalent SSH install:
104
+
105
+ ```bash
106
+ pi install git:git@github.com:aefreedman/pi-plastic@v0.4.0
107
+ ```
108
+
109
+ To intentionally track the moving default branch instead of a release tag:
110
+
111
+ ```bash
112
+ pi install https://github.com/aefreedman/pi-plastic
113
+ ```
114
+
115
+ Local development install:
116
+
117
+ ```bash
118
+ pi install <path-to-pi-plastic>
119
+ ```
120
+
121
+ Project-local install:
122
+
123
+ ```bash
124
+ pi install -l <path-to-pi-plastic>
125
+ ```
126
+
127
+ ## Requirements
128
+
129
+ - Node.js 22.19.0 or newer
130
+ - Pi 0.82.0 or newer; the current development and eval baseline is Pi 0.83
131
+ - Plastic SCM / Unity Version Control CLI (`cm`) available on `PATH`, or `PI_PLASTIC_CM_EXECUTABLE` set to its full executable path
132
+ - A compatible `diff` executable available on `PATH`, or `PI_PLASTIC_DIFF_EXECUTABLE` set to its full executable path, for text-only diff and patch tools
133
+ - A configured Plastic workspace for workspace-scoped operations
134
+ - `@aefree/pi-repo-search` is an optional peer. When installed, it receives the Plastic ignore/cloak policy; when absent, `pi-plastic` loads without repository-search integration. The tarball does not embed linked sibling workspaces or `node_modules` paths.
135
+
136
+ ## Testing
137
+
138
+ ```bash
139
+ npm test
140
+ ```
141
+
142
+ The default suite is credential-free and covers tool validation, path-resolution regressions, extension registration, OpenAI strict-schema compatibility classification, and bash guard behavior.
143
+
144
+ Run the opt-in read-only live smoke test against a dedicated clean sandbox workspace:
145
+
146
+ ```bash
147
+ PI_PLASTIC_TEST_WORKSPACE=/absolute/path/to/sandbox npm run test:live
148
+ ```
149
+
150
+ The live test requires `/main`, no pending changes, and no merge in progress. It does not mutate the repository. Mutation tools should still be rehearsed manually in a disposable sandbox before relying on them in a new environment.
151
+
152
+ ### Dynamic tool-loading eval
153
+
154
+ The package-local behavioral eval uses fresh Pi 0.83 JSON subprocesses against an explicitly attested dedicated Plastic sandbox and is not a skill eval. It compares all-active, balanced, and loader-only mode behavior, checks exact smallest-sufficient loader activations, blocks destructive calls unless they are supported `preflight: true` previews, captures tool calls and sanitized provider-schema measurements, and deletes raw provider payload captures by default.
155
+
156
+ ```bash
157
+ npm run eval:tool-loading -- --dry-run
158
+ PI_PLASTIC_EVAL_SANDBOX=/absolute/path/to/sandbox PI_PLASTIC_EVAL_ALLOW=dedicated-sandbox npm run eval:tool-loading -- --model openai-codex/gpt-5.6-luna --condition balanced
159
+ ```
160
+
161
+ See [`evals/tool-loading/README.md`](evals/tool-loading/README.md) for approved model restrictions, cases, measurements, and result hygiene.
162
+
163
+ ## Constrained sampling compatibility
164
+
165
+ Pi 0.82 introduced provider-side constrained sampling for tools. `pi-plastic` does not currently opt in: every public Plastic schema includes optional fields (at minimum `workdir`), while OpenAI strict function schemas require closed objects and all declared properties to be required. Pi forwards the registered schema without converting those optional fields.
166
+
167
+ Enabling `strict: "prefer"` now would therefore either produce invalid strict OpenAI requests or require a breaking redesign of the ordinary tool arguments. The test suite audits all registered tools and prevents accidental opt-in until a schema is genuinely strict-compatible. Existing TypeBox validation and Plastic runtime safety checks remain authoritative.
168
+
169
+ OpenAI Codex models may advertise grammar tools without advertising strict JSON-schema tools. Grammar sampling is not used here because Plastic operations have structured multi-field arguments rather than a single bounded string language.
170
+
171
+ ## Implementation notes
172
+
173
+ - The core implementation lives in `src/plastic-core.ts`.
174
+ - Shared workspace discovery and branch parsing live in `src/plastic-workspace.ts`.
175
+ - `extensions/plastic-branch-status.ts` owns the additive footer status and its session-scoped refresh lifecycle.
176
+ - `index.ts` is the Pi tool registration layer.
177
+ - Output shapes are intentionally stable for prompt and workflow compatibility.
178
+
179
+ ## License
180
+
181
+ MIT. See `LICENSE`.
package/SECURITY.md ADDED
@@ -0,0 +1,31 @@
1
+ # Security Policy
2
+
3
+ ## Supported versions
4
+
5
+ Security fixes are provided for the latest tagged release. Upgrade to the newest release before reporting an issue that may already be fixed.
6
+
7
+ ## Reporting a vulnerability
8
+
9
+ Do not open a public issue for a suspected vulnerability, credential exposure, or unsafe command-execution path.
10
+
11
+ Use GitHub's private vulnerability reporting for this repository:
12
+
13
+ <https://github.com/aefreedman/pi-plastic/security/advisories/new>
14
+
15
+ Include:
16
+
17
+ - the affected package version or commit;
18
+ - the Pi, Node.js, Git, and Plastic CLI versions;
19
+ - the operating system;
20
+ - reproduction steps and expected versus observed behavior;
21
+ - the affected tool or extension;
22
+ - impact and any known mitigations;
23
+ - whether credentials, repository content, or remote Plastic objects may have been exposed or changed.
24
+
25
+ Avoid including real credentials or sensitive repository content. Use a disposable Plastic workspace when a reproduction requires mutation.
26
+
27
+ You should receive an acknowledgement within seven days. Release timing depends on severity, reproducibility, and coordination needs.
28
+
29
+ ## Scope
30
+
31
+ Security-sensitive areas include command construction, executable-path overrides, workspace/path targeting, destructive Plastic operations, provider payload capture, dynamic tool ownership, and package-install behavior. General support questions and ordinary bugs belong in the public issue tracker.
@@ -0,0 +1,38 @@
1
+ # Dynamic tool-loading behavioral eval
2
+
3
+ This package-local eval compares `all-active`, `balanced`, and `loader-only` Plastic tool-loading modes through fresh Pi 0.83 JSON subprocesses. It is intentionally **not** a skill eval: skills, prompt templates, built-in tools, discovered extensions, context files, and sessions are disabled.
4
+
5
+ ## What it measures
6
+
7
+ - Required Plastic tool calls for status, branch listing, read-only switch/checkin preflight, compound branch list + `/main` verification, no-operation, ambiguous branch discovery, and two implicit contextual discovery prompts (inspect branches; find code reviews) that name neither the loader nor the target public tool.
8
+ - Loader behavior: exactly one `plastic_tool_search` only when a required tool is initially deferred; none for all-active, balanced cases whose target is already active, or the negative case. Whenever a loader is expected, the runner requires its activation result to be exactly the case's smallest sufficient deferred target set.
9
+ - Tool errors, assistant errors, call/output/time bounds, maximum four activation results, and no destructive activation in negative or ambiguous discovery cases. An eval-only extension blocks every destructive Plastic call unless that tool's registered schema supports `preflight` and the call supplies `preflight: true`.
10
+ - JSONL events, assistant usage/cache totals, wall time, initial provider `tools` serialization size, and native `tool_search_call` / `tool_search_output` markers when Pi/provider payloads expose them.
11
+
12
+ `baseline.json` preserves the legacy 29-tool measurement for comparison. The current all-active mode omits the loader and exposes `28 / 21071` after withholding workspace creation; balanced records `3 / 2133` (89.93% legacy reduction), and loader-only records `1 / 1121` (94.71%). The loader's prompt metadata is 278 characters; the untouched legacy baseline has none.
13
+
14
+ ## Run
15
+
16
+ A live run requires an explicit approved model and **both** sandbox attestations. `PI_PLASTIC_EVAL_SANDBOX` must resolve to a dedicated workspace containing `.pi-plastic-eval-sandbox` whose complete, exact UTF-8 content is `pi-plastic-eval-sandbox` followed by one LF newline (no other bytes):
17
+
18
+ ```text
19
+ pi-plastic-eval-sandbox
20
+ ```
21
+
22
+ For example, create the marker with `printf 'pi-plastic-eval-sandbox\n' > /absolute/path/to/sandbox/.pi-plastic-eval-sandbox`. Also set `PI_PLASTIC_EVAL_ALLOW=dedicated-sandbox`; this explicit opt-in prevents accidentally targeting an ordinary workspace. Dry runs validate configuration and do **not** require either attestation.
23
+
24
+ ```bash
25
+ PI_PLASTIC_EVAL_SANDBOX=/absolute/path/to/sandbox PI_PLASTIC_EVAL_ALLOW=dedicated-sandbox npm run eval:tool-loading -- --model openai-codex/gpt-5.6-luna
26
+ PI_PLASTIC_EVAL_SANDBOX=/absolute/path/to/sandbox PI_PLASTIC_EVAL_ALLOW=dedicated-sandbox npm run eval:tool-loading -- --model openai-codex/gpt-5.6-terra:medium --condition balanced --cases branch-listing,compound-list-branches-verify-main --trials 2
27
+ ```
28
+
29
+ Only `openai-codex/gpt-5.6-(luna|terra|sol)` with an optional supported thinking level such as `:medium` is accepted. Trials are sequential and bounded to 1–5. Never target an ordinary production workspace.
30
+
31
+ Validate the configuration without calling a model:
32
+
33
+ ```bash
34
+ npm run eval:tool-loading -- --dry-run
35
+ npm run eval:tool-loading -- --help
36
+ ```
37
+
38
+ Each trial writes provider payloads only to a unique system-temp capture file, never stdout. The runner deletes raw captures by default. Pass `--keep` only when you explicitly need those sensitive raw payloads. Sanitized timestamped summaries are written to ignored `results/`; use `--include-events` to include event type/tool-name crumbs only.
@@ -0,0 +1,57 @@
1
+ # Dynamic tool-loading pilot results
2
+
3
+ Date: 2026-07-25
4
+ Package: `@aefree/pi-plastic`
5
+ Pi: 0.82.0
6
+ Model: `openai-codex/gpt-5.6-terra:medium`
7
+ Sandbox: dedicated `plastic-tool-sandbox` repository
8
+
9
+ ## Decision
10
+
11
+ The Plastic pilot passes its rollout criteria. Keep the balanced initial set as the production default: `plastic_tool_search`, `plastic_status`, and `plastic_currentBranch`.
12
+
13
+ - Existing 29 public Plastic tools retain their names and behavior; the loader is additive.
14
+ - Balanced public metadata is 2,117 serialized characters versus the untouched 21,192-character legacy baseline, a 90.01% reduction.
15
+ - Stable live provider payloads contained 1,908 serialized initial tool characters in balanced mode versus 22,120 in the 29-tool legacy all-active mode, a 91.37% reduction.
16
+ - A deferred capability added exactly one loader call/provider round trip and no repeated loader call after activation.
17
+ - Native OpenAI deferred-loading markers (`tool_search_call` and `tool_search_output`) appeared in every deferred GPT-5.6 case and were absent when no loader call was expected.
18
+ - Core outcomes, preflight argument checks, negative controls, and ambiguous branch discovery passed.
19
+
20
+ ## Behavioral evidence
21
+
22
+ The final expanded diagnostic matrix passed 27/27 trials: nine explicit, implicit, contextual, ambiguous, compound, preflight, and negative cases under all-active, balanced, and loader-only conditions. Five stable status, branch, switch-preflight, checkin-preflight, and compound branch cases passed 45/45 trials over three repetitions per condition. A focused three-trial loader-only regression for contextual branch discovery also passed 3/3 after plural/domain query coverage was added.
23
+
24
+ Stable-trial measurements:
25
+
26
+ | Condition | Pass | Initial provider tool chars | Median wall time | Mean wall time | Mean reported total tokens | Mean tool calls |
27
+ |---|---:|---:|---:|---:|---:|---:|
28
+ | all-active | 15/15 | 22,120 | 8.78 s | 9.39 s | 7,180 | 1.20 |
29
+ | balanced | 15/15 | 1,908 | 10.50 s | 10.41 s | 3,295 | 2.00 |
30
+ | loader-only | 15/15 | 832 | 10.23 s | 10.84 s | 3,183 | 2.20 |
31
+
32
+ The balanced median added about 1.73 seconds (19.7%) in these short cases while reducing initial provider tool serialization by 91.37% and mean reported total tokens by about 54%. Mean provider requests were 2.0 all-active, 2.8 balanced, and 3.0 loader-only; deferred cases account for the accepted first-use loader request, while balanced status remained immediate. Results remain separate because cache reads, input tokens, wall time, and provider requests have different cost/latency implications.
33
+
34
+ The first diagnostic attempt used a 24,000-character JSONL capture bound and terminated normal Pi event streams before their follow-up tool turn. Raising the bounded capture limit to 2 MiB corrected the harness; no package behavior changed for the passing diagnostic and stable runs. Final live runs also required a sandbox marker, an explicit authorization environment value, and an eval-only guard that blocked mutation-capable tools unless the tool supported and received `preflight: true`.
35
+
36
+ ## Session and compatibility evidence
37
+
38
+ A persisted loader-only session first called `plastic_tool_search` and `plastic_branchList`. Its tool result stored `addedToolNames: ["plastic_branchList"]`. A fresh Pi process resumed that session, restored `plastic_branchList`, called it directly, and made no second loader call.
39
+
40
+ Package harness coverage also verifies additive activation without Pi 0.82 `sourceInfo`, representing older/fallback active-list behavior. Pi/provider fallback serialization itself is owned by Pi; this package does not claim a live non-GPT-5.6 provider test.
41
+
42
+ ## Sandbox mutation evidence
43
+
44
+ The dedicated sandbox was used for real branch, changeset, shelveset, review, merge, conflict-signal, and cleanup operations:
45
+
46
+ - Created and switched to disposable branch `/dynamic-tool-loading-eval-20260725-1538`.
47
+ - Added a synthetic fixture and created disposable changeset `cs:4`.
48
+ - Created, listed, previewed/applied, undid, and deleted shelveset `sh:2`.
49
+ - Created, found, updated, and deleted code review `21`.
50
+ - Attempted a merge to `/main`; Plastic reported an evil-twin directory conflict before establishing merge state. Explicit source finalization also stopped on that directory conflict, demonstrating the existing safe failure signal rather than silently checking in.
51
+ - Deleted the disposable branch and changeset after the stopped merge.
52
+
53
+ Final verification found `/main` active, no pending or merge state, and no remaining test branch, shelveset, or review. The private `.pi-plastic-eval-sandbox` attestation marker was removed after the eval; future guarded runs must recreate it deliberately. Workspace creation was not exercised because the package exposes create/list but no paired workspace-delete tool; leaving an account-level test workspace would not improve loader evidence.
54
+
55
+ ## Scope
56
+
57
+ This result covers the `pi-plastic` pilot only. The recorded seven-case live matrix predates the current two implicit contextual discovery cases, exact smallest-sufficient activation assertion, eval-only preflight mutation guard, and marker/authorization sandbox attestations; no live re-run is implied by those later harness hardenings. Future live runs require `PI_PLASTIC_EVAL_SANDBOX` to contain the documented `.pi-plastic-eval-sandbox` marker and `PI_PLASTIC_EVAL_ALLOW=dedicated-sandbox`. It does not authorize package publishing or version changes, and it does not begin the separate Codecks guidance design or any skill evaluation.
@@ -0,0 +1,34 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "measurement": "serializedSchemaChars is JSON.stringify of public registered tool metadata (name, description, parameters, prompt metadata, constrainedSampling); estimated tokens are chars / 4 rounded up. Live provider payload tool serialization is recorded separately by the eval runner.",
4
+ "legacyBaseline": {
5
+ "label": "untouched legacy public Plastic tools",
6
+ "toolCount": 29,
7
+ "serializedSchemaChars": 21192,
8
+ "estimatedTokens": 5298,
9
+ "promptMetadataChars": 0
10
+ },
11
+ "implementedConditions": {
12
+ "all-active": {
13
+ "toolCount": 28,
14
+ "serializedSchemaChars": 21071,
15
+ "estimatedTokens": 5268,
16
+ "promptMetadataChars": 0,
17
+ "legacySchemaReductionPercent": 0.57
18
+ },
19
+ "balanced": {
20
+ "toolCount": 3,
21
+ "serializedSchemaChars": 2133,
22
+ "estimatedTokens": 534,
23
+ "promptMetadataChars": 278,
24
+ "legacySchemaReductionPercent": 89.93
25
+ },
26
+ "loader-only": {
27
+ "toolCount": 1,
28
+ "serializedSchemaChars": 1121,
29
+ "estimatedTokens": 281,
30
+ "promptMetadataChars": 278,
31
+ "legacySchemaReductionPercent": 94.71
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,70 @@
1
+ {
2
+ "schemaVersion": 2,
3
+ "cases": [
4
+ {
5
+ "id": "status-inspection",
6
+ "prompt": "In the dedicated Plastic sandbox, inspect the workspace status now. Call plastic_status exactly once, do not call any other Plastic operation unless plastic_tool_search is required to make plastic_status available, then reply with a one-sentence status summary.",
7
+ "requiredTools": ["plastic_status"],
8
+ "minimalActivatedTools": ["plastic_status"],
9
+ "forbiddenTools": []
10
+ },
11
+ {
12
+ "id": "branch-listing",
13
+ "prompt": "In the dedicated Plastic sandbox, list the available branches. Call plastic_branchList exactly once. If it is not active, first call plastic_tool_search exactly once and activate only plastic_branchList. Do not mutate the workspace; then give a short result.",
14
+ "requiredTools": ["plastic_branchList"],
15
+ "minimalActivatedTools": ["plastic_branchList"],
16
+ "forbiddenTools": []
17
+ },
18
+ {
19
+ "id": "preflight-branch-switch",
20
+ "prompt": "Preview, but do not perform, switching the dedicated Plastic sandbox to /main. Call plastic_switchBranch exactly once with branch /main, pendingChanges cancel, preflight true, and the sandbox workdir. If the tool is inactive, first call plastic_tool_search exactly once to activate only plastic_switchBranch. Do not call any other Plastic operation, then summarize the preview.",
21
+ "requiredTools": ["plastic_switchBranch"],
22
+ "minimalActivatedTools": ["plastic_switchBranch"],
23
+ "requiredPreflightTools": ["plastic_switchBranch"],
24
+ "forbiddenTools": ["plastic_status", "plastic_currentBranch", "plastic_branchCreate", "plastic_merge"]
25
+ },
26
+ {
27
+ "id": "preflight-checkin",
28
+ "prompt": "Preview, but do not perform, a checkin in the dedicated Plastic sandbox. Call plastic_checkin exactly once with message 'dynamic-loader eval preflight', preflight true, includeAll true, and the sandbox workdir. If the tool is inactive, first call plastic_tool_search exactly once to activate only plastic_checkin. Do not add, undo, or otherwise mutate anything, then summarize the preview.",
29
+ "requiredTools": ["plastic_checkin"],
30
+ "minimalActivatedTools": ["plastic_checkin"],
31
+ "requiredPreflightTools": ["plastic_checkin"],
32
+ "forbiddenTools": ["plastic_add", "plastic_undo"]
33
+ },
34
+ {
35
+ "id": "compound-list-branches-verify-main",
36
+ "prompt": "In the dedicated Plastic sandbox, list branches and verify whether /main exists. Call plastic_branchList and plastic_branchExists once each; use /main as the branch-existence argument. If either is inactive, make exactly one plastic_tool_search call that activates both exact tools together. Do not mutate the workspace, then give a short answer.",
37
+ "requiredTools": ["plastic_branchList", "plastic_branchExists"],
38
+ "minimalActivatedTools": ["plastic_branchList", "plastic_branchExists"],
39
+ "forbiddenTools": []
40
+ },
41
+ {
42
+ "id": "negative-no-operation",
43
+ "prompt": "No Plastic operation is requested. Do not call plastic_tool_search or any plastic_* tool. Reply exactly: NO_OPERATION.",
44
+ "requiredTools": [],
45
+ "minimalActivatedTools": [],
46
+ "forbiddenTools": []
47
+ },
48
+ {
49
+ "id": "ambiguous-branch-discovery",
50
+ "prompt": "In the dedicated Plastic sandbox, safely discover branch information for a later decision. Prefer a read-only branch listing and call plastic_branchList exactly once. If it is inactive, call plastic_tool_search exactly once with a branch discovery request and activate only read-only branch tools. Do not create, switch, merge, or delete branches; then give a short answer.",
51
+ "requiredTools": ["plastic_branchList"],
52
+ "minimalActivatedTools": ["plastic_branchList"],
53
+ "forbiddenTools": ["plastic_branchCreate", "plastic_switchBranch", "plastic_merge", "plastic_mergeToBranch", "plastic_branchDelete"]
54
+ },
55
+ {
56
+ "id": "contextual-branch-inspection",
57
+ "prompt": "Before choosing a branch for later work, inspect all available branches in the dedicated sandbox exactly once and give a short read-only summary. Use only the smallest sufficient capability; do not change anything.",
58
+ "requiredTools": ["plastic_branchList"],
59
+ "minimalActivatedTools": ["plastic_branchList"],
60
+ "forbiddenTools": ["plastic_branchCreate", "plastic_switchBranch", "plastic_merge", "plastic_mergeToBranch", "plastic_branchDelete"]
61
+ },
62
+ {
63
+ "id": "contextual-review-discovery",
64
+ "prompt": "Before planning follow-up work, find the existing code reviews in the dedicated sandbox exactly once and summarize them briefly. Use only the smallest sufficient read-only capability; do not change anything.",
65
+ "requiredTools": ["plastic_codeReviewFind"],
66
+ "minimalActivatedTools": ["plastic_codeReviewFind"],
67
+ "forbiddenTools": ["plastic_codeReviewCreate", "plastic_codeReviewUpdate", "plastic_codeReviewDelete"]
68
+ }
69
+ ]
70
+ }