@aefree/pi-plastic 0.5.5 → 0.5.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 CHANGED
@@ -7,6 +7,12 @@ and this project follows semantic versioning for public package releases.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## [0.5.6] - 2026-08-27
11
+
12
+ ### Fixed
13
+
14
+ - Warn once per interactive Pi runtime when a configured `PI_PLASTIC_PATCH_EXECUTABLE` cannot launch or Windows lacks its required patch backend setting. Warnings redact configured paths; `plastic_patch` retains its existing tool-time validation.
15
+
10
16
  ## [0.5.5] - 2026-08-14
11
17
 
12
18
  ### Changed
package/README.md CHANGED
@@ -143,7 +143,7 @@ Project-local installation protects only Pi processes that load those project se
143
143
  - Pi 0.82.0 or newer; the current development and eval baseline is Pi 0.83
144
144
  - Plastic SCM / Unity Version Control CLI (`cm`) available on `PATH`, or `PI_PLASTIC_CM_EXECUTABLE` set to its full executable path
145
145
  - A GNU/POSIX-compatible text `diff` available on `PATH`, or `PI_PLASTIC_DIFF_EXECUTABLE` set to its full executable path (including paths containing spaces), for text-only diff tools. Pi invokes that resolved executable as-is and does not discover Git Bash paths automatically. On macOS, bare `diff` is PATH-dependent: it may be GNU Diffutils (often installed as `diff` ahead of system paths) or Apple’s BSD `/usr/bin/diff`; the package does not substitute one for the other. Configure the intended text executable explicitly when that distinction matters.
146
- - Patch generation has a separate executable policy: `toolPath` is the one-call highest-priority override, followed by `PI_PLASTIC_PATCH_EXECUTABLE`. On Windows, set one to a verified patch-capable non-GUI executable such as Git's `diff.exe`; the package deliberately does not reuse `PI_PLASTIC_DIFF_EXECUTABLE`/GnuWin32 as a patch default. macOS validation with Plastic 11 found that Apple BSD `/usr/bin/diff` supports text-only diffs but rejects the `--binary` argument supplied by `cm patch`; configure a verified GNU Diffutils-compatible executable explicitly for `plastic_patch`. The non-Windows fallback to `PI_PLASTIC_DIFF_EXECUTABLE` or bare `diff` remains only for environments whose resolved executable satisfies that patch contract.
146
+ - Patch generation has a separate executable policy: `toolPath` is the one-call highest-priority override, followed by `PI_PLASTIC_PATCH_EXECUTABLE`. On Windows, set one to a verified patch-capable non-GUI executable such as Git's `diff.exe`; the package deliberately does not reuse `PI_PLASTIC_DIFF_EXECUTABLE`/GnuWin32 as a patch default. macOS validation with Plastic 11 found that Apple BSD `/usr/bin/diff` supports text-only diffs but rejects the `--binary` argument supplied by `cm patch`; configure a verified GNU Diffutils-compatible executable explicitly for `plastic_patch`. The non-Windows fallback to `PI_PLASTIC_DIFF_EXECUTABLE` or bare `diff` remains only for environments whose resolved executable satisfies that patch contract. In an interactive Pi session, an invalid configured patch override or missing Windows patch setting produces one package capability warning per Pi runtime; the warning omits configured paths, and `plastic_patch` retains its tool-time validation.
147
147
  - A configured Plastic workspace for workspace-scoped operations. Text diffs require an ASCII-safe temporary directory; if the configured backend cannot accept Unicode paths, set `TEMP` and `TMP` to a writable ASCII-only location.
148
148
  - `pi-file-discovery` is an optional independently loaded integration. When its `discover_candidate_files` tool is active, it receives the advisory Plastic ignore/cloak filter through the shared global capability protocol; when absent, `pi-plastic` loads without file-discovery filtering. The tarball does not embed linked sibling workspaces or `node_modules` paths.
149
149
 
package/index.ts CHANGED
@@ -656,6 +656,28 @@ function metadataDescription(node: CoreSchemaNode): string {
656
656
  return node.metadata?.description ?? "Allowed values.";
657
657
  }
658
658
 
659
+ type CapabilityDiagnosticContext = {
660
+ hasUI?: boolean;
661
+ ui?: { notify?: (message: string, level: "warning") => void };
662
+ };
663
+
664
+ type PatchCapabilityWarningGetter = () => Promise<string | undefined>;
665
+
666
+ export const __plasticCapabilityDiagnosticInternals = {
667
+ createPatchCapabilityWarningNotifier(getWarning: PatchCapabilityWarningGetter = () => core.getPatchBackendCapabilityWarning()) {
668
+ let emitted = false;
669
+ return async (ctx: CapabilityDiagnosticContext): Promise<void> => {
670
+ if (emitted || !ctx.hasUI || typeof ctx.ui?.notify !== "function") return;
671
+ const warning = await getWarning();
672
+ if (!warning) return;
673
+ emitted = true;
674
+ ctx.ui.notify(warning, "warning");
675
+ };
676
+ },
677
+ };
678
+
679
+ const notifyPatchCapabilityWarning = __plasticCapabilityDiagnosticInternals.createPatchCapabilityWarningNotifier();
680
+
659
681
  function buildParameters(args: Record<string, unknown> | undefined): TSchema {
660
682
  if (!args || Object.keys(args).length === 0) {
661
683
  return EMPTY_PARAMETERS;
@@ -770,7 +792,8 @@ export default function plasticTools(pi: ExtensionAPI) {
770
792
  },
771
793
  });
772
794
 
773
- pi.on("session_start", (_event, ctx) => {
795
+ pi.on("session_start", async (_event, ctx) => {
796
+ await notifyPatchCapabilityWarning(ctx as CapabilityDiagnosticContext);
774
797
  const ownership = getEffectivePlasticToolOwnership(pi.getAllTools(), EXTENSION_SOURCE_PATH);
775
798
  const active = pi.getActiveTools();
776
799
  const mode = getPlasticToolLoadingMode();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aefree/pi-plastic",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22.19.0"
@@ -18,8 +18,7 @@
18
18
  "skills/",
19
19
  "CHANGELOG.md",
20
20
  "LICENSE",
21
- "README.md",
22
- "SECURITY.md"
21
+ "README.md"
23
22
  ],
24
23
  "pi": {
25
24
  "extensions": [
@@ -34,7 +33,7 @@
34
33
  ]
35
34
  },
36
35
  "scripts": {
37
- "test": "npx --yes tsx tests/plastic-tool-validation.ts && npx --yes tsx tests/plastic-status-machine-readable.test.ts && npx --yes tsx tests/plastic-checkin-path-resolution.ts && npx --yes tsx tests/plastic-diff-workflow.test.ts && npx --yes tsx tests/plastic-workspace-diff.test.ts && npx --yes tsx tests/plastic-process-lifecycle.test.ts && npx --yes tsx tests/plastic-patch-command.test.ts && npx --yes tsx tests/plastic-extension-registration.test.ts && npx --yes tsx tests/plastic-dynamic-tool-loading.test.ts && npx --yes tsx tests/plastic-tool-rendering.test.ts && npx --yes tsx tests/plastic-branch-query.test.ts && npx --yes tsx tests/plastic-workspace.test.ts && npx --yes tsx tests/plastic-mutation-execution.test.ts && npx --yes tsx tests/file-discovery-filter.test.ts && npx --yes tsx tests/file-discovery-filter-registration.test.ts && npx --yes tsx tests/packed-copy-owner-conflict.test.ts && npx --yes tsx tests/plastic-branch-status.test.ts && npx --yes tsx tests/plastic-constrained-sampling.test.ts && npx --yes tsx tests/bash-cm-diff-guard-validation.ts",
36
+ "test": "npx --yes tsx tests/plastic-tool-validation.ts && npx --yes tsx tests/plastic-status-machine-readable.test.ts && npx --yes tsx tests/plastic-checkin-path-resolution.ts && npx --yes tsx tests/plastic-diff-workflow.test.ts && npx --yes tsx tests/plastic-workspace-diff.test.ts && npx --yes tsx tests/plastic-process-lifecycle.test.ts && npx --yes tsx tests/plastic-patch-command.test.ts && npx --yes tsx tests/plastic-capability-diagnostics.test.ts && npx --yes tsx tests/plastic-extension-registration.test.ts && npx --yes tsx tests/plastic-dynamic-tool-loading.test.ts && npx --yes tsx tests/plastic-tool-rendering.test.ts && npx --yes tsx tests/plastic-branch-query.test.ts && npx --yes tsx tests/plastic-workspace.test.ts && npx --yes tsx tests/plastic-mutation-execution.test.ts && npx --yes tsx tests/file-discovery-filter.test.ts && npx --yes tsx tests/file-discovery-filter-registration.test.ts && npx --yes tsx tests/packed-copy-owner-conflict.test.ts && npx --yes tsx tests/plastic-branch-status.test.ts && npx --yes tsx tests/plastic-constrained-sampling.test.ts && npx --yes tsx tests/bash-cm-diff-guard-validation.ts",
38
37
  "test:live": "npx --yes tsx tests/plastic-live-smoke.test.ts",
39
38
  "test:live:branch-create": "npx --yes tsx tests/plastic-branch-create-live.test.ts",
40
39
  "eval:tool-loading": "npx --yes tsx evals/tool-loading/run-eval.ts"
@@ -22,7 +22,7 @@ Plastic merges are workspace- and branch-oriented, not Git fast-forwards: the me
22
22
 
23
23
  `plastic_workspaceDiff` requires selected paths or explicit `allPending=true`, runs status once, has small default file/per-file/combined-output bounds, and returns per-file outcomes without aborting when one file is unavailable. Selected private paths are included; whole-workspace private review additionally requires `includePrivate=true`. Keep `maxChars` small unless more output is intentional. Text-diff selection and patch-backend selection are distinct: text tools use `PI_PLASTIC_DIFF_EXECUTABLE` or bare PATH `diff`. On macOS, bare `diff` can resolve either to GNU Diffutils or Apple’s BSD `/usr/bin/diff`; Pi uses the resolved executable and does not auto-select `gdiff`.
24
24
 
25
- Use `plastic_patch` for review patch generation with `clean` and `integration` filters; it is generation-only and does not apply patches. `toolPath` is its highest-priority one-call patch-backend override, followed by `PI_PLASTIC_PATCH_EXECUTABLE`. On Windows, configure one to a verified patch-capable non-GUI executable such as Git's `diff.exe`: patch generation deliberately does not reuse `PI_PLASTIC_DIFF_EXECUTABLE`/GnuWin32. On macOS with Plastic 11, Apple BSD `/usr/bin/diff` works for text-only `plastic_diffFile` but rejects the `--binary` argument used by `cm patch`; set the patch-specific policy to a verified GNU Diffutils-compatible non-GUI executable. The non-Windows fallback to the text setting or bare `diff` is only valid when that executable accepts the local Plastic patch arguments. Unqualified `br:/...` patch selectors are qualified only from the current workspace's exact selector repository; otherwise pass `br:/<branch>@<repository>@<server>`. For a large patch, pass an intentional new `output` path: the package stages and atomically publishes it, never overwriting an existing path. Do not use `cm cat --raw`: it is unsupported for this workflow. Typed retrieval owns cleanup; the low-level byte-preserving fallback is `cm cat --file` only when a typed tool cannot represent the case. Plastic/server output may represent moves as moves or delete/add records, so inspect generated patches rather than assuming a move-aware encoding.
25
+ Use `plastic_patch` for review patch generation with `clean` and `integration` filters; it is generation-only and does not apply patches. `toolPath` is its highest-priority one-call patch-backend override, followed by `PI_PLASTIC_PATCH_EXECUTABLE`. On Windows, configure one to a verified patch-capable non-GUI executable such as Git's `diff.exe`: patch generation deliberately does not reuse `PI_PLASTIC_DIFF_EXECUTABLE`/GnuWin32. On macOS with Plastic 11, Apple BSD `/usr/bin/diff` works for text-only `plastic_diffFile` but rejects the `--binary` argument used by `cm patch`; set the patch-specific policy to a verified GNU Diffutils-compatible non-GUI executable. The non-Windows fallback to the text setting or bare `diff` is only valid when that executable accepts the local Plastic patch arguments. Interactive Pi sessions issue one path-redacted package capability warning per runtime for an invalid configured patch override or missing Windows setting; fix `PI_PLASTIC_PATCH_EXECUTABLE` and retry, while headless sessions retain `plastic_patch` tool-time validation. Unqualified `br:/...` patch selectors are qualified only from the current workspace's exact selector repository; otherwise pass `br:/<branch>@<repository>@<server>`. For a large patch, pass an intentional new `output` path: the package stages and atomically publishes it, never overwriting an existing path. Do not use `cm cat --raw`: it is unsupported for this workflow. Typed retrieval owns cleanup; the low-level byte-preserving fallback is `cm cat --file` only when a typed tool cannot represent the case. Plastic/server output may represent moves as moves or delete/add records, so inspect generated patches rather than assuming a move-aware encoding.
26
26
 
27
27
  Prefer runtime `plastic_*` tools first. Keep `cm` shell commands as manual fallback.
28
28
 
@@ -32,6 +32,7 @@ const getCmExecutable = (): string => resolveExecutable(process.env, "PI_PLASTIC
32
32
  // Keep the executable name configurable rather than assuming a .exe suffix or package-manager path.
33
33
  const getDiffExecutable = (): string => resolveExecutable(process.env, "PI_PLASTIC_DIFF_EXECUTABLE", "diff");
34
34
  const PLASTIC_PATCH_EXECUTABLE_ENV = "PI_PLASTIC_PATCH_EXECUTABLE";
35
+ type PatchExecutableProbe = (command: string) => Promise<boolean>;
35
36
  // Plastic's server/backend determines whether a moved item is encoded as a
36
37
  // move or as delete/add records. Do not claim either without a live fixture.
37
38
  const PATCH_MOVE_REPRESENTATION = "backend-determined" as const;
@@ -1228,6 +1229,49 @@ const assertNonBlankPatchValue = (name: keyof PatchCommandArgs, value: string |
1228
1229
  }
1229
1230
  };
1230
1231
 
1232
+ const probePatchExecutable = async (command: string): Promise<boolean> =>
1233
+ {
1234
+ try
1235
+ {
1236
+ // This establishes only that the configured executable can launch.
1237
+ // `cm patch` remains the authoritative verifier for its --binary contract.
1238
+ const result = await spawnAndCollect(command, ["--version"], process.cwd(), undefined, AbortSignal.timeout(3000), {
1239
+ abortKillDelayMs: 100,
1240
+ outputLimitChars: 1024,
1241
+ });
1242
+ return !result.aborted;
1243
+ }
1244
+ catch
1245
+ {
1246
+ return false;
1247
+ }
1248
+ };
1249
+
1250
+ export const getPatchBackendCapabilityWarning = async (
1251
+ environment: ExecutableEnvironment = process.env,
1252
+ platform: NodeJS.Platform = process.platform,
1253
+ probe: PatchExecutableProbe = probePatchExecutable,
1254
+ ): Promise<string | undefined> =>
1255
+ {
1256
+ const configuredPatchExecutable = environment[PLASTIC_PATCH_EXECUTABLE_ENV]?.trim();
1257
+ if (configuredPatchExecutable)
1258
+ {
1259
+ if (await probe(configuredPatchExecutable))
1260
+ {
1261
+ return undefined;
1262
+ }
1263
+
1264
+ return "Pi Plastic capability warning: the configured PI_PLASTIC_PATCH_EXECUTABLE could not be launched. Set it to a verified patch-capable non-GUI diff executable, or remove the override to use this platform's documented fallback. The configured path is not shown for privacy.";
1265
+ }
1266
+
1267
+ if (platform === "win32")
1268
+ {
1269
+ return "Pi Plastic capability warning: plastic_patch requires PI_PLASTIC_PATCH_EXECUTABLE on Windows. Set it to a verified patch-capable non-GUI diff executable (for example Git's diff.exe). PI_PLASTIC_DIFF_EXECUTABLE is only for text diffs.";
1270
+ }
1271
+
1272
+ return undefined;
1273
+ };
1274
+
1231
1275
  const resolvePatchToolPath = (
1232
1276
  toolPath?: string,
1233
1277
  environment: ExecutableEnvironment = process.env,
@@ -1491,6 +1535,7 @@ export const __plasticProcessInternals = {
1491
1535
  export const __plasticPatchInternals = {
1492
1536
  buildPatchCommandArgs,
1493
1537
  resolvePatchToolPath,
1538
+ getPatchBackendCapabilityWarning,
1494
1539
  qualifyPatchBranchSpec,
1495
1540
  resolvePatchBranchSpecs,
1496
1541
  createPatchOutputStaging,
package/SECURITY.md DELETED
@@ -1,31 +0,0 @@
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.