pi-shorthand 0.12.0 → 0.13.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.
package/README.md CHANGED
@@ -40,6 +40,8 @@ Programs edit a private workspace, with host files outside the repository kept r
40
40
 
41
41
  The `code` tool uses Pi's working directory by default. Pass `cwd` to target another checkout; relative paths are resolved from Pi's working directory. For example, when Pi starts in a bare worktree container, `cwd: "child"` targets its `child` worktree. The chosen directory must be inside a Git worktree.
42
42
 
43
+ Scripts run by Pi's `codemode` tool can call `code` too. They get back what the run changed as data, not text.
44
+
43
45
  ## Without Pi
44
46
 
45
47
  The engine is published separately as [shorthand-code](https://github.com/sebinsua/shortsight/tree/main/packages/shorthand-code), with a `shorthand` command that runs a program from a file or stdin and prints the same result. See `shorthand --help`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-shorthand",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Pi tool for editing repositories with Bun programs, with first-class support for TypeScript and JavaScript.",
5
5
  "keywords": [
6
6
  "ast-grep",
@@ -26,7 +26,7 @@
26
26
  ],
27
27
  "type": "module",
28
28
  "dependencies": {
29
- "shorthand-code": "0.12.0"
29
+ "shorthand-code": "0.13.0"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "@earendil-works/pi-ai": "*",
package/src/index.ts CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  } from "shorthand-code";
23
23
  import { callLine, resultLines, unstructuredResultText } from "./display.ts";
24
24
 
25
- const DESCRIPTION = `Edit repository files with a TypeScript program run by Bun. Best for changes across many files, repeated edits and semantic TypeScript renames or moves; a small change to one file is quicker as a direct edit. Top-level await and ordinary Bun/Node APIs work. Use repository-relative paths. Set cwd to a checkout path when Pi's working directory is outside the repository, such as a child worktree in a bare worktree container. The program runs in an isolated workspace; changes apply on successful exit by default and the tool reports the diff. Run tests, type-checks and builds separately afterward with the shell tool.
25
+ const DESCRIPTION = `Edit repository files with a TypeScript program run by Bun. Best for changes across many files, repeated edits and semantic TypeScript renames or moves; a small change to one file is quicker as a direct edit. Top-level await and ordinary Bun/Node APIs work. Use repository-relative paths. Set cwd to a checkout path when Pi's working directory is outside the repository, such as a child worktree in a bare worktree container. The program runs in an isolated workspace; changes apply on successful exit by default and the tool reports the diff. Tests, type-checks and builds belong outside the program: in a shell call, or, when a codemode script calls code, later in that script.
26
26
 
27
27
  Common operations:
28
28
  - edit({ path, oldText, newText }) replaces exactly one literal occurrence; missing or ambiguous text is an error. Use text edits for known source, structural matching when it saves enumerating occurrences or preserves varying syntax.
@@ -51,6 +51,59 @@ export function codeDescription(graphAvailable: boolean): string {
51
51
  : DESCRIPTION;
52
52
  }
53
53
 
54
+ const paths = (description: string) => Type.Array(Type.String(), { description });
55
+
56
+ /**
57
+ * What a script gets back from `code`, for example one run by Pi's codemode tool. The model still sees the text.
58
+ * A failed run resolves to this too, so a script checks `exitCode`.
59
+ */
60
+ export const CODE_OUTPUT_SCHEMA = Type.Object({
61
+ exitCode: Type.Optional(
62
+ Type.Union([Type.Number(), Type.Null()], { description: "0 on success; null when the program was killed" }),
63
+ ),
64
+ timedOut: Type.Optional(Type.Boolean()),
65
+ output: Type.Optional(Type.String({ description: "The program's stdout and stderr" })),
66
+ errorLine: Type.Optional(Type.String({ description: "On failure, the program line the error came from" })),
67
+ warnings: Type.Optional(Type.Array(Type.String(), { description: "Likely mistakes spotted before it ran" })),
68
+ changes: Type.Optional(
69
+ Type.Array(
70
+ Type.Object({
71
+ path: Type.String(),
72
+ kind: StringEnum(["added", "modified", "deleted"] as const),
73
+ patch: Type.String(),
74
+ }),
75
+ ),
76
+ ),
77
+ applied: Type.Optional(paths("Changed files written to the repository")),
78
+ conflicts: Type.Optional(paths("Files that changed outside the run while it ran; if any, nothing was applied")),
79
+ rolledBack: Type.Optional(paths("Changed files not kept after a failure")),
80
+ infrastructureError: Type.Optional(Type.String({ description: "Set, alone, when the program couldn't be run" })),
81
+ });
82
+
83
+ export function codeOutput(result: RunResult) {
84
+ const { exitCode, timedOut, output, errorLine, warnings, applied, conflicts, rolledBack } = result;
85
+ const changes = result.changes.map((change) => ({ path: change.path, kind: change.kind, patch: change.patch }));
86
+ return {
87
+ exitCode,
88
+ timedOut,
89
+ output,
90
+ ...(errorLine === undefined ? {} : { errorLine }),
91
+ warnings,
92
+ changes,
93
+ applied,
94
+ conflicts,
95
+ rolledBack,
96
+ };
97
+ }
98
+
99
+ /**
100
+ * Base name of the temporary files that hold a large run's full diff or output. A call a codemode script makes has
101
+ * an id like `<parent id>/1`, and ids can hold `|`, so anything but a safe file-name character is replaced.
102
+ */
103
+ export function spillName(toolCallId: string): string {
104
+ return `pi-shorthand-${toolCallId.replace(/[^\w.-]/g, "_")}`;
105
+ }
106
+
54
107
  export default async function (pi: ExtensionAPI, findGraph: () => Promise<unknown> = sightreadAvailable) {
55
108
  // The shorthand skill ships with shorthand-code, whose `shorthand --skill` prints it for other agents.
56
109
  pi.on("resources_discover", () => ({ skillPaths: [SKILLS_DIRECTORY] }));
@@ -67,7 +120,7 @@ export default async function (pi: ExtensionAPI, findGraph: () => Promise<unknow
67
120
  label: "Code",
68
121
  description: codeDescription(graphAvailable),
69
122
  promptSnippet:
70
- "Make multi-file, repetitive or rename/move changes with one Bun program; a small change to one file is quicker as a direct edit. Run verification separately afterward",
123
+ "Make multi-file, repetitive or rename/move changes with one Bun program; a small change to one file is quicker as a direct edit. Keep verification outside the program",
71
124
 
72
125
  parameters: Type.Object({
73
126
  title: Type.String({ description: "A few words describing the change, shown to the user" }),
@@ -90,6 +143,7 @@ export default async function (pi: ExtensionAPI, findGraph: () => Promise<unknow
90
143
  }),
91
144
  ),
92
145
  }),
146
+ outputSchema: CODE_OUTPUT_SCHEMA,
93
147
 
94
148
  async execute(toolCallId, params, signal, onUpdate, ctx) {
95
149
  // While it runs, show how long it's been going and the latest reported step.
@@ -122,6 +176,7 @@ export default async function (pi: ExtensionAPI, findGraph: () => Promise<unknow
122
176
  isError: true,
123
177
  content: [{ type: "text" as const, text: [error.message, ...diagnosticLines(error.diagnostics)].join("\n") }],
124
178
  details: { infrastructureError: error.message, diagnostics: error.diagnostics },
179
+ structuredContent: { infrastructureError: error.message },
125
180
  };
126
181
  } finally {
127
182
  clearInterval(progress);
@@ -130,10 +185,11 @@ export default async function (pi: ExtensionAPI, findGraph: () => Promise<unknow
130
185
  content: [
131
186
  {
132
187
  type: "text",
133
- text: textForModel(result, { name: `pi-shorthand-${toolCallId}`, truncateHead, truncateTail }),
188
+ text: textForModel(result, { name: spillName(toolCallId), truncateHead, truncateTail }),
134
189
  },
135
190
  ],
136
191
  details: result,
192
+ structuredContent: codeOutput(result),
137
193
  };
138
194
  },
139
195