@aefree/pi-plastic 0.5.3 → 0.5.5
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 +15 -0
- package/README.md +4 -3
- package/extensions/plastic-branch-status.ts +2 -1
- package/index.ts +2 -0
- package/package.json +2 -2
- package/src/plastic-core.ts +52 -3
- package/src/plastic-workspace.ts +9 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project follows semantic versioning for public package releases.
|
|
7
7
|
|
|
8
|
+
## Unreleased
|
|
9
|
+
|
|
10
|
+
## [0.5.5] - 2026-08-14
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Made `plastic_status(machineReadable=true, format="json")` return compact parsed pending-item records, aggregate counts, and bounded-item metadata, with package-owned field separation and revision IDs. It returns at most 100 items by default (configurable up to 500); raw, unbounded Plastic output is omitted by default and available with `includeRaw=true` only for diagnostics.
|
|
15
|
+
|
|
16
|
+
## [0.5.4] - 2026-08-13
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Recognize both `smartbranch` and `br` branch forms in Plastic workspace selectors, including quoted values produced by different checkout workflows.
|
|
21
|
+
- Use a valid local selector as the footer's authoritative branch source instead of launching an unnecessary `cm status` process; malformed or missing branch selectors retain the bounded CLI fallback.
|
|
22
|
+
|
|
8
23
|
## [0.5.3] - 2026-08-09
|
|
9
24
|
|
|
10
25
|
### Changed
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Pi tools, footer status, and skill guidance for Plastic SCM / Unity Version Cont
|
|
|
4
4
|
|
|
5
5
|
## Plastic branch footer status
|
|
6
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
|
|
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` and reads either the selector's `smartbranch` or `br` form as the credential-free branch source. A bounded `cm status` call is used only when the selector has no valid branch. Selector changes and successful same-workspace `plastic_*` tools refresh the status; sibling workspaces are ignored.
|
|
8
8
|
|
|
9
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
10
|
|
|
@@ -74,6 +74,7 @@ The Bash guards run only in Pi processes that actually load this package. A proj
|
|
|
74
74
|
|
|
75
75
|
- `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.
|
|
76
76
|
- `plastic_diff` remains a disabled alias by design. Use `plastic_status` for changed-path listing; do not diff as routine post-edit validation or checkin preflight. When change-boundary evidence is needed, use `plastic_diffFile` for one exact file, `plastic_workspaceDiff` with selected paths or explicit `allPending=true`, or `plastic_diffRevisions` for an explicit historical pair. Diff responses use small defaults and caller-controlled `maxChars` bounds. Historical and workspace bytes are copied to ASCII-safe package-owned paths before the backend runs, while normalized headers retain the logical Unicode labels.
|
|
77
|
+
- `plastic_status(machineReadable=true, format="json")` returns parsed `items`, aggregate `summary`, and `itemCount` (`total`, `returned`, `omitted`) rather than requiring agents to parse Plastic rows. It returns at most 100 items by default; set `maxItems` (up to 500) when more are intentional. `includeRaw=true` adds the original unbounded Plastic output for diagnostics, so omit it for normal agent use to conserve context tokens.
|
|
77
78
|
- Preflight is not routine confirmation. Use it for ambiguous or broad mutation scope, moved/deleted path rewriting, compound operations, or explicit preview requests; otherwise rely on exact targets and the tools' runtime guards.
|
|
78
79
|
- `plastic_patch` generates review patches with `cm patch`, including `clean` and `integration` filters for branch review workflows. It does not expose patch apply. Unqualified `br:/...` selectors are qualified only with the current workspace selector's exact repository; otherwise pass `br:/<branch>@<repository>@<server>`.
|
|
79
80
|
- Bash safety rails block `cm diff` and unsafe interactive `cm merge --merge` usage.
|
|
@@ -107,13 +108,13 @@ pi install npm:@aefree/pi-plastic
|
|
|
107
108
|
Install a pinned GitHub release over HTTPS:
|
|
108
109
|
|
|
109
110
|
```bash
|
|
110
|
-
pi install git:github.com/aefreedman/pi-plastic@v0.5.
|
|
111
|
+
pi install git:github.com/aefreedman/pi-plastic@v0.5.4
|
|
111
112
|
```
|
|
112
113
|
|
|
113
114
|
Equivalent SSH install:
|
|
114
115
|
|
|
115
116
|
```bash
|
|
116
|
-
pi install git:git@github.com/aefreedman/pi-plastic@v0.5.
|
|
117
|
+
pi install git:git@github.com/aefreedman/pi-plastic@v0.5.4
|
|
117
118
|
```
|
|
118
119
|
|
|
119
120
|
To intentionally track the moving default branch instead of a release tag:
|
|
@@ -219,7 +219,8 @@ export function createPlasticBranchStatusExtension(overrides: Partial<BranchStat
|
|
|
219
219
|
if (!active || generation !== ownedGeneration || refreshSequence !== ownedRefresh) return;
|
|
220
220
|
|
|
221
221
|
const hasSelectorBranch = selectorBranch ? displayBranch(selectorBranch) : false;
|
|
222
|
-
if (
|
|
222
|
+
if (hasSelectorBranch) return;
|
|
223
|
+
displayUnavailable();
|
|
223
224
|
|
|
224
225
|
const controller = new AbortController();
|
|
225
226
|
confirmationController = controller;
|
package/index.ts
CHANGED
|
@@ -94,6 +94,8 @@ const TOOL_CONFIG: Partial<Record<PlasticExportName, ToolConfig>> = {
|
|
|
94
94
|
normalizeWorkdirAliases(input);
|
|
95
95
|
assignAlias(input, "includeRevId", ["include_rev_id"]);
|
|
96
96
|
assignAlias(input, "machineReadable", ["machine_readable", "machine"]);
|
|
97
|
+
assignAlias(input, "maxItems", ["max_items"]);
|
|
98
|
+
assignAlias(input, "includeRaw", ["include_raw"]);
|
|
97
99
|
return input;
|
|
98
100
|
},
|
|
99
101
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aefree/pi-plastic",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=22.19.0"
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
]
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
|
-
"test": "npx --yes tsx tests/plastic-tool-validation.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",
|
|
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",
|
|
38
38
|
"test:live": "npx --yes tsx tests/plastic-live-smoke.test.ts",
|
|
39
39
|
"test:live:branch-create": "npx --yes tsx tests/plastic-branch-create-live.test.ts",
|
|
40
40
|
"eval:tool-loading": "npx --yes tsx evals/tool-loading/run-eval.ts"
|
package/src/plastic-core.ts
CHANGED
|
@@ -326,6 +326,20 @@ type PendingItemSummary = {
|
|
|
326
326
|
privatePaths: string[];
|
|
327
327
|
};
|
|
328
328
|
|
|
329
|
+
type MachineReadableStatusItem = {
|
|
330
|
+
statusCode: string;
|
|
331
|
+
kind: PendingItemKind;
|
|
332
|
+
path: string;
|
|
333
|
+
isDirectory: boolean;
|
|
334
|
+
revisionId?: string;
|
|
335
|
+
sourcePath?: string;
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
type MachineReadableStatusSummary = Omit<PendingItemSummary, "privatePaths">;
|
|
339
|
+
|
|
340
|
+
const MACHINE_READABLE_STATUS_DEFAULT_MAX_ITEMS = 100;
|
|
341
|
+
const MACHINE_READABLE_STATUS_MAX_ITEMS = 500;
|
|
342
|
+
|
|
329
343
|
type PrivateAutoAddSelection = {
|
|
330
344
|
candidatePaths: string[];
|
|
331
345
|
blockedPaths: Array<{ path: string; reason: string }>;
|
|
@@ -685,6 +699,22 @@ const getMachineReadablePendingItems = async (workdir?: string): Promise<Pending
|
|
|
685
699
|
return parseMachineReadablePendingItems(output, cwd);
|
|
686
700
|
};
|
|
687
701
|
|
|
702
|
+
const toMachineReadableStatusItems = (pendingItems: PendingItem[]): MachineReadableStatusItem[] =>
|
|
703
|
+
pendingItems.map((item) => ({
|
|
704
|
+
statusCode: item.statusCode,
|
|
705
|
+
kind: item.kind,
|
|
706
|
+
path: item.workspacePath,
|
|
707
|
+
isDirectory: item.isDirectory,
|
|
708
|
+
...(item.revisionId ? { revisionId: item.revisionId } : {}),
|
|
709
|
+
...(item.sourceWorkspacePath ? { sourcePath: item.sourceWorkspacePath } : {}),
|
|
710
|
+
}));
|
|
711
|
+
|
|
712
|
+
const toMachineReadableStatusSummary = (pendingItems: PendingItem[], cwd: string): MachineReadableStatusSummary =>
|
|
713
|
+
{
|
|
714
|
+
const { privatePaths: _privatePaths, ...summary } = summarizePendingItems(pendingItems, cwd);
|
|
715
|
+
return summary;
|
|
716
|
+
};
|
|
717
|
+
|
|
688
718
|
const summarizePendingItems = (pendingItems: PendingItem[], cwd?: string): PendingItemSummary =>
|
|
689
719
|
{
|
|
690
720
|
const summary: PendingItemSummary = {
|
|
@@ -2375,7 +2405,9 @@ export const status = tool({
|
|
|
2375
2405
|
args: {
|
|
2376
2406
|
workdir: workdirArg,
|
|
2377
2407
|
includeRevId: tool.schema.boolean().optional().describe("Include revision IDs in the status output when supported."),
|
|
2378
|
-
machineReadable: tool.schema.boolean().optional().describe("Return machine-readable status
|
|
2408
|
+
machineReadable: tool.schema.boolean().optional().describe("Return parsed, machine-readable pending status records when supported."),
|
|
2409
|
+
maxItems: tool.schema.number().int().min(1).max(MACHINE_READABLE_STATUS_MAX_ITEMS).optional().describe(`Maximum parsed items in machine-readable JSON (default ${MACHINE_READABLE_STATUS_DEFAULT_MAX_ITEMS}, maximum ${MACHINE_READABLE_STATUS_MAX_ITEMS}).`),
|
|
2410
|
+
includeRaw: tool.schema.boolean().optional().describe("Include unbounded raw machine-readable Plastic output in JSON diagnostics. Applies only when machineReadable=true."),
|
|
2379
2411
|
short: tool.schema.boolean().optional().describe("Use short status output."),
|
|
2380
2412
|
format: outputFormatArg,
|
|
2381
2413
|
},
|
|
@@ -2396,15 +2428,32 @@ export const status = tool({
|
|
|
2396
2428
|
|
|
2397
2429
|
if (args.machineReadable)
|
|
2398
2430
|
{
|
|
2399
|
-
|
|
2431
|
+
// Package-owned separators make paths with whitespace unambiguous, and
|
|
2432
|
+
// revision IDs let agents safely identify the base of moved/deleted items.
|
|
2433
|
+
if (!cmdArgs.includes("--includeRevId"))
|
|
2434
|
+
{
|
|
2435
|
+
cmdArgs.push("--includeRevId");
|
|
2436
|
+
}
|
|
2437
|
+
cmdArgs.push("--machinereadable", `--fieldseparator=${STATUS_FIELD_SEPARATOR}`);
|
|
2400
2438
|
const machineOutput = await runCmRaw(cmdArgs, args.workdir);
|
|
2439
|
+
const cwd = args.workdir ?? process.cwd();
|
|
2440
|
+
const pendingItems = parseMachineReadablePendingItems(machineOutput, cwd);
|
|
2441
|
+
const maxItems = args.maxItems ?? MACHINE_READABLE_STATUS_DEFAULT_MAX_ITEMS;
|
|
2442
|
+
const returnedItems = pendingItems.slice(0, maxItems);
|
|
2401
2443
|
return toStructuredResult(
|
|
2402
2444
|
"status",
|
|
2403
2445
|
format,
|
|
2404
2446
|
machineOutput,
|
|
2405
2447
|
{
|
|
2406
|
-
rawOutput: machineOutput,
|
|
2407
2448
|
machineReadable: true,
|
|
2449
|
+
items: toMachineReadableStatusItems(returnedItems),
|
|
2450
|
+
itemCount: {
|
|
2451
|
+
total: pendingItems.length,
|
|
2452
|
+
returned: returnedItems.length,
|
|
2453
|
+
omitted: pendingItems.length - returnedItems.length,
|
|
2454
|
+
},
|
|
2455
|
+
summary: toMachineReadableStatusSummary(pendingItems, cwd),
|
|
2456
|
+
...(args.includeRaw ? { rawOutput: machineOutput } : {}),
|
|
2408
2457
|
},
|
|
2409
2458
|
args.workdir,
|
|
2410
2459
|
);
|
package/src/plastic-workspace.ts
CHANGED
|
@@ -78,7 +78,8 @@ const parseSelectorValue = (line: string, key: string): string | undefined => {
|
|
|
78
78
|
/** Parse the local selector without treating it as proof that a workspace exists. */
|
|
79
79
|
export function parsePlasticSelector(content: string): ParseOutcome<PlasticSelector> {
|
|
80
80
|
let repository: string | undefined;
|
|
81
|
-
let
|
|
81
|
+
let smartBranch: string | undefined;
|
|
82
|
+
let explicitBranch: string | undefined;
|
|
82
83
|
let sawBranchKey = false;
|
|
83
84
|
|
|
84
85
|
for (const line of content.split(/\r?\n/)) {
|
|
@@ -86,13 +87,18 @@ export function parsePlasticSelector(content: string): ParseOutcome<PlasticSelec
|
|
|
86
87
|
if (/^\s*repository(?:\s|$)/i.test(line)) repository = parseSelectorValue(line, "repository");
|
|
87
88
|
if (/^\s*smartbranch(?:\s|$)/i.test(line)) {
|
|
88
89
|
sawBranchKey = true;
|
|
89
|
-
|
|
90
|
+
smartBranch = parseSelectorValue(line, "smartbranch");
|
|
91
|
+
}
|
|
92
|
+
if (/^\s*br(?:\s|$)/i.test(line)) {
|
|
93
|
+
sawBranchKey = true;
|
|
94
|
+
explicitBranch = parseSelectorValue(line, "br");
|
|
90
95
|
}
|
|
91
96
|
}
|
|
92
97
|
|
|
93
98
|
if (!sawBranchKey) return { kind: "not_found" };
|
|
99
|
+
const branch = smartBranch ?? explicitBranch;
|
|
94
100
|
if (!branch || /[\u0000-\u001f\u007f]/.test(branch)) {
|
|
95
|
-
return { kind: "malformed", reason: "Selector
|
|
101
|
+
return { kind: "malformed", reason: "Selector branch is blank or contains control characters." };
|
|
96
102
|
}
|
|
97
103
|
|
|
98
104
|
return {
|