@aefree/pi-plastic 0.8.0 → 0.8.2

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,18 @@ and this project follows semantic versioning for public package releases.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## [0.8.2] - 2026-10-13
11
+
12
+ - Add focused real-Pi codemode declaration coverage for existing Plastic input constraints, including explicit absence of schema patterns/defaults.
13
+
14
+ - Refresh exact Pi development dependencies to stable 1.2.0; derive validation expectations from package.json and retain strict installed-version checks.
15
+
16
+ ## [0.8.1] - 2026-10-12
17
+
18
+ ### Fixed
19
+
20
+ - Restored useful bounded direct status text and parseable JSON for standard, machine and XML sources, including exact admitted paths/moves, merge hints, uncertainty and presentation omissions. Native DTOs remain unchanged; opt-in raw diagnostics are separately bounded and cannot restore excluded machine identities.
21
+
10
22
  ## [0.8.0] - 2026-10-08
11
23
 
12
24
  ### Added
@@ -16,7 +16,7 @@ npm pack --dry-run
16
16
 
17
17
  Run commands from the manifest root. `npm test` starts with `npm run typecheck`: strict no-emit coverage includes index/src/extensions and compile-only schema-inference contracts. Runtime tests/evals use tsx.
18
18
 
19
- The current pinned development/eval SDK and TUI baseline is **1.1.0**, as declared in package.json; older Pi releases are not a supported matrix. Historical fixture/evidence version strings are not alternate runtime targets. Host-provided framework packages remain peers, not bundled runtime dependencies.
19
+ The current pinned development/eval SDK and TUI baseline is the exact version, as declared in package.json; older Pi releases are not a supported matrix. Historical fixture/evidence version strings are not alternate runtime targets. Host-provided framework packages remain peers, not bundled runtime dependencies.
20
20
 
21
21
  Default tests are credential-free. They cover schemas/semantic guards, registration/loading, aliases, paths/snapshots, lifecycles/retirement, rendering, Bash guards, documentation/navigation, ownership and constrained-sampling classification. The SDK-host harness uses real file loading/finalization/direct/codemode/nested consumers and JSON execution events, with a deterministic local provider and synthetic CLI—no network/backend-effect acceptance.
22
22
 
@@ -78,7 +78,7 @@ For behavior changes, report the narrow actual live test and resulting-state/cle
78
78
 
79
79
  ## Tool-loading eval
80
80
 
81
- The package-owned behavioral eval is not a skill eval. Fresh SDK 1.1.0 JSON subprocesses compare all-active/balanced/loader-only mode, smallest-sufficient activations and sanitized provider-schema measurements.
81
+ The package-owned behavioral eval is not a skill eval. Fresh manifest-selected SDK JSON subprocesses compare all-active/balanced/loader-only mode, smallest-sufficient activations and sanitized provider-schema measurements.
82
82
 
83
83
  Dry-run validates configuration without live provider trials. Actual trials require an explicitly attested dedicated Plastic sandbox and an approved model. Destructive calls are blocked unless supported preflight previews; raw payload captures are deleted by default.
84
84
 
@@ -99,7 +99,21 @@ The tested Windows client emitted OEM 437 bytes for some Latin text and replaced
99
99
 
100
100
  Paths: 4,096 characters; revision IDs: 128; status codes: 64; returned rows: 100 default/500 maximum; compact DTO: 131,072 UTF-8 bytes. Unsupported identity-bearing fields are excluded, not clipped. DTO overflow fails closed.
101
101
 
102
- Legacy machine/standard raw presentation (`includeRaw`, `details.rawResult`) and subprocess capture are not bounded by this DTO cap. Omit raw output for normal agent use. Text adds no version lookup; legacy JSON can use a cached `cm version` observation outside DTO provenance.
102
+ ### Registered-tool presentation contract
103
+
104
+ These presentation limits are separate from the unchanged native DTO/domain/resource limits above:
105
+
106
+ | Channel / admitted facts | Limit / overflow policy |
107
+ |---|---|
108
+ | Direct text or parseable JSON, also retained as renderer `details.rawResult` | 32,768 UTF-8 bytes; remove whole trailing rows/lines, disclose presentation returned/omitted counts; never clip an identity |
109
+ | Machine/XML presentation rows | Only validated native rows (100 default/500 maximum); path/source/revision/status bounds unchanged |
110
+ | Standard presentation lines | 100 default/500 maximum, 4,096 code units per whole line; exclude lossy/oversized lines, preserve exact remaining text and disclose omissions |
111
+ | Opt-in raw diagnostic excerpt | 8,192 UTF-8 bytes, whole lines only, source-labelled with privacy warning and omission counts; machine raw withheld if native read/projection is incomplete |
112
+ | Native DTO | Unchanged 131,072 UTF-8 bytes; overflow remains a structured failure |
113
+
114
+ Presentation carries summary, source/read/capture uncertainty, native projection omissions and standard merge-hint counts independently of the DTO. Direct JSON `itemCount` describes its displayed rows; `nativeItemCount` retains the unchanged DTO row accounting, and `presentation` reports additional selection/exclusion. Additional presentation omissions do not change native completeness. Standard lines are external text, not verified reusable path records or conflict/readiness evidence. `includeRaw` never bypasses identity exclusions; omit it for normal agent use. Registered JSON presentation adds no version lookup. The exported legacy `status` operation keeps its existing presentation interface.
115
+
116
+ Outcome mapping is unchanged: successful partial machine reads remain `ok:true`; standard read is unknown; strict XML rejects unsupported/unsafe source data; operation/capture/DTO failures return `isError:true` and bounded `ok:false`. Presentation does not rerun commands, change effects, or authorize replay.
103
117
 
104
118
  ## Standard
105
119
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aefree/pi-plastic",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22.19.0"
@@ -37,17 +37,18 @@
37
37
  "typecheck": "tsc -p tsconfig.json",
38
38
  "pretest": "npm run typecheck",
39
39
  "test:docs": "tsx tests/plastic-documentation.test.ts",
40
- "test": "tsx tests/plastic-documentation.test.ts && tsx tests/eval-mutation-guard.test.ts && tsx tests/tool-definition.test.ts && tsx tests/release-registry-preflight.test.ts && tsx tests/plastic-tool-validation.ts && tsx tests/plastic-status-machine-readable.test.ts && tsx tests/plastic-status-output.test.ts && tsx tests/plastic-status-xml.test.ts && tsx tests/plastic-branch-output.test.ts && tsx tests/plastic-checkin-output.test.ts && tsx tests/plastic-branch-create-output.test.ts && tsx tests/plastic-switch-output.test.ts && tsx tests/plastic-update-output.test.ts && tsx tests/plastic-add-output.test.ts && tsx tests/plastic-undo-output.test.ts && tsx tests/plastic-removal-output.test.ts && tsx tests/plastic-workspace-merge-output.test.ts && tsx tests/plastic-workspace-merge-closeout.test.ts && tsx tests/plastic-closeout-output.test.ts && tsx tests/plastic-closeout-parent.test.ts && tsx tests/plastic-copied-closeout.test.ts && tsx tests/plastic-copied-cardinality.test.ts && tsx tests/plastic-branch-delete-output.test.ts && tsx tests/plastic-object-delete-output.test.ts && tsx tests/plastic-object-write-output.test.ts && tsx tests/plastic-receipt-integration.test.ts && tsx tests/plastic-checkin-core-closeout.test.ts && tsx tests/plastic-selected-collector-retirement.test.ts && tsx tests/plastic-checkin-path-resolution.ts && tsx tests/plastic-diff-workflow.test.ts && tsx tests/plastic-diff-revisions-output.test.ts && tsx tests/plastic-local-snapshot.test.ts && tsx tests/plastic-workspace-diff.test.ts && tsx tests/plastic-process-lifecycle.test.ts && tsx tests/plastic-module-boundaries.test.ts && tsx tests/plastic-patch-command.test.ts && tsx tests/plastic-patch-output.test.ts && tsx tests/plastic-capability-diagnostics.test.ts && tsx tests/plastic-extension-registration.test.ts && tsx tests/plastic-dynamic-tool-loading.test.ts && tsx tests/plastic-tool-rendering.test.ts && tsx tests/plastic-rendering-runtime-isolation.test.ts && tsx tests/plastic-branch-query.test.ts && tsx tests/plastic-workspace.test.ts && tsx tests/plastic-workspace-list-output.test.ts && tsx tests/plastic-shelveset-list-output.test.ts && tsx tests/plastic-code-review-find-output.test.ts && tsx tests/plastic-mutation-execution.test.ts && tsx tests/plastic-merge-branches.test.ts && tsx tests/plastic-server-merge-output.test.ts && tsx tests/plastic-server-merge-resolutions.test.ts && tsx tests/file-discovery-filter.test.ts && tsx tests/file-discovery-filter-registration.test.ts && tsx tests/packed-copy-owner-conflict.test.ts && tsx tests/plastic-branch-status.test.ts && tsx tests/plastic-constrained-sampling.test.ts && tsx tests/bash-cm-diff-guard-validation.ts",
40
+ "test": "tsx tests/pi-version-selection.test.ts && tsx tests/plastic-documentation.test.ts && tsx tests/eval-mutation-guard.test.ts && tsx tests/tool-definition.test.ts && tsx tests/release-registry-preflight.test.ts && tsx tests/plastic-tool-validation.ts && tsx tests/plastic-status-machine-readable.test.ts && tsx tests/plastic-status-output.test.ts && tsx tests/plastic-status-xml.test.ts && tsx tests/plastic-branch-output.test.ts && tsx tests/plastic-checkin-output.test.ts && tsx tests/plastic-branch-create-output.test.ts && tsx tests/plastic-switch-output.test.ts && tsx tests/plastic-update-output.test.ts && tsx tests/plastic-add-output.test.ts && tsx tests/plastic-undo-output.test.ts && tsx tests/plastic-removal-output.test.ts && tsx tests/plastic-workspace-merge-output.test.ts && tsx tests/plastic-workspace-merge-closeout.test.ts && tsx tests/plastic-closeout-output.test.ts && tsx tests/plastic-closeout-parent.test.ts && tsx tests/plastic-copied-closeout.test.ts && tsx tests/plastic-copied-cardinality.test.ts && tsx tests/plastic-branch-delete-output.test.ts && tsx tests/plastic-object-delete-output.test.ts && tsx tests/plastic-object-write-output.test.ts && tsx tests/plastic-receipt-integration.test.ts && tsx tests/plastic-checkin-core-closeout.test.ts && tsx tests/plastic-selected-collector-retirement.test.ts && tsx tests/plastic-checkin-path-resolution.ts && tsx tests/plastic-diff-workflow.test.ts && tsx tests/plastic-diff-revisions-output.test.ts && tsx tests/plastic-local-snapshot.test.ts && tsx tests/plastic-workspace-diff.test.ts && tsx tests/plastic-process-lifecycle.test.ts && tsx tests/plastic-module-boundaries.test.ts && tsx tests/plastic-patch-command.test.ts && tsx tests/plastic-patch-output.test.ts && tsx tests/plastic-capability-diagnostics.test.ts && tsx tests/plastic-extension-registration.test.ts && tsx tests/plastic-dynamic-tool-loading.test.ts && tsx tests/plastic-tool-rendering.test.ts && tsx tests/plastic-rendering-runtime-isolation.test.ts && tsx tests/plastic-branch-query.test.ts && tsx tests/plastic-workspace.test.ts && tsx tests/plastic-workspace-list-output.test.ts && tsx tests/plastic-shelveset-list-output.test.ts && tsx tests/plastic-code-review-find-output.test.ts && tsx tests/plastic-mutation-execution.test.ts && tsx tests/plastic-merge-branches.test.ts && tsx tests/plastic-server-merge-output.test.ts && tsx tests/plastic-server-merge-resolutions.test.ts && tsx tests/file-discovery-filter.test.ts && tsx tests/file-discovery-filter-registration.test.ts && tsx tests/packed-copy-owner-conflict.test.ts && tsx tests/plastic-branch-status.test.ts && tsx tests/plastic-constrained-sampling.test.ts && tsx tests/bash-cm-diff-guard-validation.ts",
41
41
  "preview:tools": "tsx tests/plastic-tool-rendering.test.ts --preview",
42
42
  "test:host:status": "tsx tests/pi-host-status-output.test.ts",
43
+ "test:host:declarations": "tsx tests/pi-host-declarations.test.ts",
43
44
  "test:live": "tsx tests/plastic-live-smoke.test.ts",
44
45
  "test:live:branch-create": "tsx tests/plastic-branch-create-live.test.ts",
45
46
  "eval:tool-loading": "tsx evals/tool-loading/run-eval.ts"
46
47
  },
47
48
  "devDependencies": {
48
49
  "@aefree/pi-file-discovery": "^0.1.1",
49
- "@earendil-works/pi-coding-agent": "1.1.0",
50
- "@earendil-works/pi-tui": "1.1.0",
50
+ "@earendil-works/pi-coding-agent": "1.2.0",
51
+ "@earendil-works/pi-tui": "1.2.0",
51
52
  "@types/node": "22.20.4",
52
53
  "tsx": "4.23.15",
53
54
  "typebox": "^1.3.8",
@@ -1,6 +1,6 @@
1
1
  import { Type, type Static, type TSchema } from "typebox";
2
2
  import { Check } from "typebox/value";
3
- import { assembleStatusObservation, presentStatusObservation, type StatusObservationOptions, type StatusObservation, StatusOptionError, StatusXmlError } from "../operations/status";
3
+ import { assembleStatusObservation, type StatusObservationOptions, type StatusObservation, StatusOptionError, StatusXmlError } from "../operations/status";
4
4
  import { StatusCommandError } from "../execution/status-command";
5
5
 
6
6
  export const STATUS_OUTPUT_MAX_BYTES = 131072;
@@ -130,19 +130,74 @@ export function projectStatusOutput(observation: StatusObservation, maxItems = 1
130
130
  });
131
131
  }
132
132
 
133
+ // Presentation ceilings, independent of native DTO and transport/domain limits.
134
+ export const STATUS_PRESENTATION_MAX_BYTES = 32768;
135
+ export const STATUS_RAW_PRESENTATION_MAX_BYTES = 8192;
136
+ const safeLine = (line: string) => !line.includes("�") && !(process.platform === "win32" && line.includes("?")) && !/[\u0000-\u0008\u000b\u000c\u000e-\u001e]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/.test(line);
137
+ const lines = (text: string) => text.split(/\r?\n/).filter(line => line.trim().length > 0);
138
+
139
+ /** Render native admitted rows, not legacy raw presentation. No command/version lookup. */
140
+ function presentStatusOutput(observation: StatusObservation, dto: Extract<StatusOutput, { ok: true }>, args: { format?: "text" | "json"; maxItems?: number; includeRaw?: boolean }): string {
141
+ const standardLines = observation.kind === "standard" ? lines(observation.output) : [];
142
+ const admittedLines = standardLines.filter(line => line.length <= 4096 && safeLine(line));
143
+ const selectedLines = admittedLines.slice(0, args.maxItems ?? 100);
144
+ const selectedItems = dto.data.mode === "standard" ? [] : [...dto.data.items];
145
+ const rawSource = observation.kind === "xml" ? "status_xml (utf-8)" : observation.kind === "machine" ? "status --machinereadable" : "status text";
146
+ const rawWarning = observation.kind === "xml" ? "Raw XML contains private workspace/repository/server metadata." : "Raw status contains private workspace/repository/server metadata.";
147
+ // A diagnostic must not resurrect machine identities withheld by native admission.
148
+ const rawWithheld = observation.kind === "machine" && (dto.completeness.read !== "complete" || !dto.completeness.projection);
149
+ const rawLines = args.includeRaw && !rawWithheld ? lines(observation.output) : [];
150
+ const selectedRaw: string[] = [];
151
+ let rawBytes = 0;
152
+ for (const line of rawLines) {
153
+ const bytes = Buffer.byteLength(line, "utf8") + (selectedRaw.length ? 1 : 0);
154
+ if (!safeLine(line) || (observation.kind === "standard" && line.length > 4096) || rawBytes + bytes > STATUS_RAW_PRESENTATION_MAX_BYTES) continue;
155
+ selectedRaw.push(line); rawBytes += bytes;
156
+ }
157
+ const render = () => {
158
+ const presentation = dto.data.mode === "standard"
159
+ ? { basis: "external_text_lines", total: standardLines.length, returned: selectedLines.length, omitted: standardLines.length - selectedLines.length, excluded: standardLines.length - admittedLines.length }
160
+ : { basis: "native_admitted_rows", total: dto.data.itemCount.parsed, returned: selectedItems.length, omitted: dto.data.itemCount.parsed - selectedItems.length, excluded: dto.data.itemCount.excluded };
161
+ const raw = args.includeRaw ? { source: rawSource, warning: rawWarning, withheld: rawWithheld, text: selectedRaw.join("\n"), returnedLines: selectedRaw.length, omittedLines: rawWithheld ? null : rawLines.length - selectedRaw.length } : undefined;
162
+ const data = dto.data.mode === "standard"
163
+ ? { ...dto.data, lines: selectedLines, presentation }
164
+ : { ...dto.data, items: selectedItems, nativeItemCount: dto.data.itemCount, itemCount: { parsed: presentation.total, returned: presentation.returned, omitted: presentation.omitted, excluded: presentation.excluded }, presentation };
165
+ if (args.format === "json") return JSON.stringify({ ok: true, action: "status", completeness: dto.completeness, data, ...(raw ? { rawOutput: raw } : {}) });
166
+ const heading = dto.data.mode === "standard" ? `Status (standard text): ${dto.data.summary.totalPending} classified short-output lines.` : `Status ${dto.data.mode} (synthesized): ${dto.data.itemCount.parsed} parsed records.`;
167
+ const rows = dto.data.mode === "standard" ? selectedLines : selectedItems.map(row => `${row.statusCode} ${row.sourcePath ? `${row.sourcePath} -> ` : ""}${row.path}${row.isDirectory ? " [directory]" : ""}${"revisionId" in row ? ` [revision ${row.revisionId}]` : ""}`);
168
+ const hints = dto.data.mode === "standard" ? [`Merge hints (text_hints): ${dto.data.mergeHints.pendingMergeLinkCount} pending merge links; ${dto.data.mergeHints.mergeInProgressHintCount} merge-in-progress hints (not verified conflicts).`] : [];
169
+ return [heading, `Read: ${dto.completeness.read}; capture: ${dto.completeness.capture}; native projection: ${dto.completeness.projection ? "complete" : "incomplete"}.`, `Summary: ${JSON.stringify(dto.data.summary)}`, ...hints, ...rows,
170
+ `Presentation: ${presentation.returned} returned; ${presentation.omitted} omitted; ${presentation.excluded} excluded.`,
171
+ ...(dto.data.mode !== "standard" ? [`Native rows: ${dto.data.itemCount.returned} returned; ${dto.data.itemCount.omitted} omitted; ${dto.data.itemCount.excluded} excluded.`] : []),
172
+ ...(dto.data.mode === "xml" ? ["Workspace scope; absolute paths; base revisions unavailable."] : []),
173
+ ...(raw ? [raw.warning, `Raw ${raw.source}: ${raw.withheld ? "withheld due to incomplete native admission" : `${raw.returnedLines} lines returned; ${raw.omittedLines} omitted`}`, raw.text] : []),
174
+ ].join("\n");
175
+ };
176
+ let result = render();
177
+ while (Buffer.byteLength(result, "utf8") > STATUS_PRESENTATION_MAX_BYTES) {
178
+ // Drop whole records only; JSON is serialized after selection, never byte-sliced.
179
+ if (selectedRaw.length) selectedRaw.pop();
180
+ else if (selectedItems.length) selectedItems.pop();
181
+ else if (selectedLines.length) selectedLines.pop();
182
+ else throw new Error("Status fixed presentation exceeded its ceiling.");
183
+ result = render();
184
+ }
185
+ return result;
186
+ }
187
+
133
188
  export async function executeStatusOutput(args: StatusObservationOptions & { format?: "text" | "json"; maxItems?: number; includeRaw?: boolean }) {
134
189
  let dto: StatusOutput;
135
190
  let rawResult: string | undefined;
136
191
  try {
137
192
  const observation = await assembleStatusObservation(args);
138
193
  dto = projectStatusOutput(observation, args.maxItems);
139
- if (dto.ok) rawResult = await presentStatusObservation(observation, args);
194
+ if (dto.ok) rawResult = presentStatusOutput(observation, dto, args);
140
195
  } catch (error) {
141
196
  const version = args.source === "xml" ? 2 : 1;
142
197
  dto = validateStatusOutput(error instanceof StatusXmlError ? ({ ...envelope, schemaVersion: 2, ok: false, completeness: { read: "incomplete", capture: "complete", projection: false }, error: { code: error.code, message: error.message } }) : error instanceof StatusCommandError ? errorOutput(error.code, error.message, version) : error instanceof StatusOptionError ? errorOutput("command_failed", error.message, version) : errorOutput("invalid_producer_data", "Status producer failed to produce valid data.", version), version);
143
198
  }
144
199
  return {
145
- content: [{ type: "text" as const, text: dto.ok ? dto.data.mode !== "standard" ? `Status: ${dto.data.itemCount.parsed} parsed records; ${dto.data.itemCount.returned} returned. Read: ${dto.completeness.read}.` : `Status: ${dto.data.summary.totalPending} classified short-output lines. Read: unknown.` : dto.error.message }],
200
+ content: [{ type: "text" as const, text: dto.ok ? rawResult! : args.format === "json" ? JSON.stringify(dto) : dto.error.message }],
146
201
  structuredContent: dto, isError: !dto.ok,
147
202
  details: { exportName: "status", rawResult, ...(args.workdir && (dto.ok || args.source !== "xml") ? { workdir: args.workdir } : {}) },
148
203
  };