pi-usereq 0.51.0 → 0.56.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 +190 -130
- package/package.json +6 -5
- package/scripts/lib/sdk-smoke.ts +62 -4
- package/src/core/extension-status.ts +3 -1
- package/src/core/pi-notify.ts +1 -1
- package/src/core/prompt-command-runtime.ts +3 -3
- package/src/core/prompts.ts +17 -18
- package/src/core/req-references-command.ts +45 -11
- package/src/core/settings-menu.ts +2 -2
- package/src/index.ts +161 -104
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# PI-useReq/pi-usereq (0.
|
|
1
|
+
# PI-useReq/pi-usereq (0.56.0)
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<img src="https://img.shields.io/badge/python-3.11%2B-3776AB?style=flat-square&logo=python&logoColor=white" alt="Python 3.11+">
|
|
@@ -9,19 +9,22 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
-
<strong>
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
<strong>pi-usereq is a pi extension that runs a requirements-first development workflow.</strong><br>
|
|
13
|
+
It turns a User Request into a living <em>Software Requirements Specification</em> (SRS), implements the corresponding
|
|
14
|
+
source code, and keeps the project documentation (<code>WORKFLOW.md</code>, <code>REFERENCES.md</code>, <code>FLOWCHART.md</code>, <code>README.md</code>)
|
|
15
|
+
in sync with the repository. All capabilities are exposed as slash commands and agent tools inside
|
|
16
|
+
<a href="https://pi.dev"><strong>pi</strong></a> (<code>pi-coding-agent</code> 0.80.4+).
|
|
15
17
|
</p>
|
|
16
18
|
|
|
17
19
|
<p align="center">
|
|
18
20
|
<a href="#quick-start">Quick Start</a> |
|
|
21
|
+
<a href="#requirements">Requirements</a> |
|
|
19
22
|
<a href="#feature-highlights">Feature Highlights</a> |
|
|
20
23
|
<a href="#prompts-and-agents">Prompts and Agents</a> |
|
|
21
24
|
<a href="#default-workflow">Default Workflow</a> |
|
|
22
|
-
<a href="#
|
|
23
|
-
<a href="#
|
|
24
|
-
<a href="#
|
|
25
|
+
<a href="#install-uninstall">Install/Uninstall</a> |
|
|
26
|
+
<a href="#extension-usage">Extension Usage</a> |
|
|
27
|
+
<a href="#note-on-git-usage">Note on Git usage</a>
|
|
25
28
|
</p>
|
|
26
29
|
<p align="center">
|
|
27
30
|
<br>
|
|
@@ -31,38 +34,66 @@ This allows them to be run both as a Python package (installed as <b>req</b>, <b
|
|
|
31
34
|
<p>
|
|
32
35
|
|
|
33
36
|
|
|
37
|
+
## Quick Start
|
|
34
38
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
-
|
|
39
|
+
1. **Install the extension** (see [Install](#install)) and restart pi.
|
|
40
|
+
2. **Configure the project** (optional): run `/pi-usereq` to set the documentation directory, unit-tests directory, source directories, static code checkers, enabled tools, git automation, and notifications.
|
|
41
|
+
3. **Write the requirements**: run `/req-write <User Request>` to produce a first SRS draft, or `/req-create` to derive the SRS from the existing source code.
|
|
42
|
+
4. **Implement the source code**: run `/req-implement` to build the source from the requirements, or `/req-cover` to make the minimal changes that cover uncovered requirements.
|
|
43
|
+
5. **Update the documentation**: run `/req-workflow`, `/req-flowchart`, and/or `/req-references` to regenerate the project documentation from the source; `/req-readme` keeps this file aligned with the implementation.
|
|
44
|
+
6. **Iterate**: use `/req-change`, `/req-new`, `/req-fix`, `/req-refactor`, and `/req-check` to evolve requirements and code together.
|
|
45
|
+
7. **Recover** (if a run fails or is interrupted): run `/req-reset` to restore the original base path and remove generated worktrees and branches.
|
|
38
46
|
|
|
39
47
|
|
|
40
|
-
##
|
|
41
|
-
- TODO: complete the bulle list with feature highlights
|
|
48
|
+
## Requirements
|
|
42
49
|
|
|
50
|
+
- **pi CLI** (`pi.dev`) - the extension runs inside pi; requires `@earendil-works/pi-coding-agent` 0.80.4 or newer (Node.js 22.19+, per the pi CLI requirement).
|
|
51
|
+
- **Git repository** - every `req-*` command runs slash-command-owned git validation: the project must be inside a git work tree, the tracked working tree must be clean, and `HEAD` must resolve (a detached `HEAD` is tolerated; a working branch is recommended because the branch name is embedded in generated worktree names).
|
|
52
|
+
- **Requirements documentation** - the configured `docs-dir` (default `pi-usereq/docs`) must contain the canonical documents required by each command (`REQUIREMENTS.md`, `WORKFLOW.md`, `REFERENCES.md`); commands such as `/req-write`, `/req-create`, and `/req-workflow` are the entry points that generate them.
|
|
53
|
+
- **Static code checkers** - the bundled checkers (`pyright`, `ruff`, `eslint`) install automatically through the `postinstall` script; the native C/C++ checkers (`cppcheck`, `clang-format`) require a one-line system install (see [Install](#install)). Default configured languages: C, C++, JavaScript, Python, TypeScript.
|
|
43
54
|
|
|
44
|
-
## Extension Custom Commands
|
|
45
55
|
|
|
46
|
-
|
|
56
|
+
## Feature Highlights
|
|
47
57
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
- **17 slash commands** - 15 prompt-backed `req-*` commands plus the dedicated non-agentic `/req-references` and `/req-reset` commands.
|
|
59
|
+
- **11 built-in agent tools** - token counting, summarization, compression, construct search, and static-check tools for explicit files or for the configured project surface.
|
|
60
|
+
- **Interactive configuration menu** - `/pi-usereq` manages local (`<base-path>/.pi-usereq.json`) and global (`~/.config/pi-usereq/config.json`) configuration with no manual JSON editing required.
|
|
61
|
+
- **Worktree-isolated runs** - prompt-command executions run in an isolated git worktree (created with `<prefix><project>-<branch>-<YYYYMMDDHHMMSS>` names) and are merged back with a stash-assisted fast-forward on success.
|
|
62
|
+
- **Automatic commit guidance** - `AUTO_GIT_COMMIT=enable` (default) injects structured commit instructions (`<TYPE>(<COMPONENT>): <DESCRIPTION> [useReq]`) into every prompt; disabling it forces read-only git behavior and turns worktree orchestration off.
|
|
63
|
+
- **Notifications** - desktop notify command, sound effects (levels `none`/`low`/`mid`/`high`, default `alt+s` toggle) and Pushover push messages on prompt completion, interruption, or failure.
|
|
64
|
+
- **Runtime status bar** - the extension renders its workflow state, current branch, context usage, elapsed time, and sound level in the pi status line.
|
|
65
|
+
- **Debug surface** - config-gated `debug-*` slash commands and a standalone debug harness (`scripts/debug-extension.ts`, `scripts/pi-usereq-debug.sh`) for offline inspection and replay.
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
## Prompts and Agents
|
|
69
|
+
|
|
70
|
+
Each `req-*` command is invoked as `/req-<name> <User Request>` inside pi. Prompt commands first run git validation and the
|
|
71
|
+
prompt-specific required-document checks, then (when `Auto git commit` and `Git worktree` are enabled) switch the session into an
|
|
72
|
+
isolated worktree, render the bundled prompt with the project context, and on success merge the changes back and leave the
|
|
73
|
+
repository clean. `/req-references` and `/req-reset` are non-agentic: they execute directly without starting an LLM session or
|
|
74
|
+
creating a worktree.
|
|
75
|
+
|
|
76
|
+
| Command | Description | Required docs |
|
|
77
|
+
| --- | --- | --- |
|
|
78
|
+
| `/req-write` | Produce a *SRS* draft based on the User Request description | none |
|
|
79
|
+
| `/req-create` | Write a *Software Requirements Specification* using the project's source code | none |
|
|
80
|
+
| `/req-recreate` | Reorganize and update the *Software Requirements Specification* based on source code analysis (preserve requirement IDs) | `REQUIREMENTS.md`, `WORKFLOW.md`, `REFERENCES.md` |
|
|
81
|
+
| `/req-renumber` | Deterministically renumber requirement IDs in the *Software Requirements Specification* without changing requirement text or order | `REQUIREMENTS.md`, `WORKFLOW.md`, `REFERENCES.md` |
|
|
82
|
+
| `/req-analyze` | Produce an analysis report | `REQUIREMENTS.md`, `WORKFLOW.md`, `REFERENCES.md` |
|
|
83
|
+
| `/req-change` | Update the requirements and implement the corresponding changes | `REQUIREMENTS.md`, `WORKFLOW.md`, `REFERENCES.md` |
|
|
84
|
+
| `/req-check` | Run the requirements check | `REQUIREMENTS.md`, `WORKFLOW.md`, `REFERENCES.md` |
|
|
85
|
+
| `/req-cover` | Implement minimal changes to cover uncovered existing requirements | `REQUIREMENTS.md`, `WORKFLOW.md`, `REFERENCES.md` |
|
|
86
|
+
| `/req-fix` | Fix a defect without changing the requirements | `REQUIREMENTS.md`, `WORKFLOW.md`, `REFERENCES.md` |
|
|
87
|
+
| `/req-implement` | Implement source code from requirements | `REQUIREMENTS.md` |
|
|
88
|
+
| `/req-new` | Implement a new requirement and the corresponding source code changes | `REQUIREMENTS.md`, `WORKFLOW.md`, `REFERENCES.md` |
|
|
89
|
+
| `/req-refactor` | Perform a refactor without changing the requirements | `REQUIREMENTS.md`, `WORKFLOW.md`, `REFERENCES.md` |
|
|
90
|
+
| `/req-readme` | Write `README.md` from user-visible implementation evidence | `REQUIREMENTS.md`, `WORKFLOW.md`, `REFERENCES.md` |
|
|
91
|
+
| `/req-references` | Write a `REFERENCES.md` using the project's source code (non-agentic, commits the regenerated file) | none |
|
|
92
|
+
| `/req-workflow` | Write a `WORKFLOW.md` using the project's source code | none |
|
|
93
|
+
| `/req-flowchart` | Write a `FLOWCHART.md` using the project's source code | `REQUIREMENTS.md`, `WORKFLOW.md`, `REFERENCES.md` |
|
|
94
|
+
| `/req-reset` | Reset the req workflow state, restore the base path, and remove generated worktrees and branches (non-agentic) | none |
|
|
95
|
+
|
|
96
|
+
Bundled prompts substitute project-derived values through `%%` placeholders, including `%%SRC_PATHS%%` (configured source directories), `%%ARGS%%` (command arguments), `%%PROMPT%%` (command name), `%%COMMIT%%` (git commit or read-only instruction), and `%%CONTEXT_FILES%%` (the canonical docs, when the corresponding context-file toggles are enabled in the settings menu).
|
|
66
97
|
|
|
67
98
|
|
|
68
99
|
## Default Workflow
|
|
@@ -74,47 +105,48 @@ Click to zoom flowchart image.
|
|
|
74
105
|
|
|
75
106
|
## Project's Documentation
|
|
76
107
|
|
|
77
|
-
|
|
78
108
|
### Project's Tree
|
|
79
109
|
|
|
80
|
-
TODO: update/rewrite the project tree
|
|
81
|
-
|
|
82
110
|
```text
|
|
83
111
|
.
|
|
84
|
-
├── .
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
│
|
|
88
|
-
│
|
|
89
|
-
│
|
|
90
|
-
│
|
|
91
|
-
├──
|
|
92
|
-
|
|
93
|
-
├──
|
|
94
|
-
|
|
95
|
-
└──
|
|
96
|
-
└──
|
|
112
|
+
├── .pi-usereq.json # local project configuration (auto-generated)
|
|
113
|
+
├── pi-usereq/ # configured docs-dir (default)
|
|
114
|
+
│ └── docs/
|
|
115
|
+
│ ├── REQUIREMENTS.md # Software Requirements Specification (SRS)
|
|
116
|
+
│ ├── WORKFLOW.md # runtime/execution units and call traces
|
|
117
|
+
│ ├── REFERENCES.md # symbol index generated from the source
|
|
118
|
+
│ └── FLOWCHART.md # workflow flowchart (generated on demand)
|
|
119
|
+
├── src/ # source code
|
|
120
|
+
├── tests/ # unit tests suite
|
|
121
|
+
├── scripts/ # debug harness and checker installer
|
|
122
|
+
├── images/ # project assets (flowchart, logo)
|
|
123
|
+
└── ~/.config/pi-usereq/ # global configuration (outside the repo)
|
|
124
|
+
└── config.json # cross-project settings (checkers, git, notifications)
|
|
97
125
|
```
|
|
98
126
|
|
|
127
|
+
The documentation directory, unit-tests directory, and source directories are configurable via the
|
|
128
|
+
[settings menu](#extension-usage) (defaults: `pi-usereq/docs`, `tests`, `src`).
|
|
129
|
+
|
|
130
|
+
|
|
99
131
|
## Install/Uninstall
|
|
100
132
|
|
|
101
133
|
### Install
|
|
102
134
|
|
|
103
|
-
Install:
|
|
135
|
+
Install the extension package with the pi CLI:
|
|
136
|
+
|
|
104
137
|
```bash
|
|
105
138
|
pi install npm:pi-usereq
|
|
106
139
|
```
|
|
107
140
|
|
|
108
|
-
Or
|
|
141
|
+
Or install it directly from the git repository:
|
|
142
|
+
|
|
109
143
|
```bash
|
|
110
144
|
pi install git:github.com/Ogekuri/PI-useReq
|
|
111
145
|
```
|
|
112
146
|
|
|
113
|
-
Reload
|
|
147
|
+
Reload pi (restart the session).
|
|
114
148
|
|
|
115
|
-
Bundled static checkers (`pyright`, `ruff`, `eslint`) install automatically via the
|
|
116
|
-
`postinstall` script. Native checkers (`cppcheck`, `clang-format`) require a one-line
|
|
117
|
-
system install:
|
|
149
|
+
Bundled static checkers (`pyright`, `ruff`, `eslint`) install automatically via the `postinstall` script. Native checkers (`cppcheck`, `clang-format`) require a one-line system install:
|
|
118
150
|
|
|
119
151
|
- Debian/Ubuntu: `sudo apt install cppcheck clang-format`
|
|
120
152
|
- macOS: `brew install cppcheck clang-format`
|
|
@@ -123,33 +155,29 @@ system install:
|
|
|
123
155
|
|
|
124
156
|
### Uninstall
|
|
125
157
|
|
|
126
|
-
|
|
158
|
+
Remove the extension package with the pi CLI (use the same source you installed from):
|
|
127
159
|
|
|
128
160
|
```bash
|
|
129
|
-
|
|
161
|
+
pi remove npm:pi-usereq
|
|
130
162
|
```
|
|
131
163
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
TODO: complete/reeview with a quick start guide with a complete quick start guide
|
|
164
|
+
Or, if it was installed from git:
|
|
135
165
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
4. Use `/req-workflow`, `/req-flowchart`, and/or `/req-references` to update project's documentation.
|
|
140
|
-
5. Start to use `/req-change`, `/req-new`, and `/req-fix`.
|
|
166
|
+
```bash
|
|
167
|
+
pi remove git:github.com/Ogekuri/PI-useReq
|
|
168
|
+
```
|
|
141
169
|
|
|
142
|
-
|
|
170
|
+
Then reload pi. Optionally delete the persisted configuration files: the global `~/.config/pi-usereq/config.json`
|
|
171
|
+
and the project-local `.pi-usereq.json` files.
|
|
143
172
|
|
|
144
|
-
TODO: document all extension features in details
|
|
145
173
|
|
|
146
|
-
|
|
174
|
+
## Extension Usage
|
|
147
175
|
|
|
148
|
-
|
|
176
|
+
> The bulk of the extension capabilities are covered by the [Prompts and Agents](#prompts-and-agents) table; this section documents the tools, the standalone CLI, the settings menu, and the side features.
|
|
149
177
|
|
|
150
178
|
### Extension Custom Tools
|
|
151
179
|
|
|
152
|
-
|
|
180
|
+
The extension registers the following agent tools (available to the LLM inside pi). All parameters are exposed through the pi tool-call contract; `FILE` entries may be project-relative or absolute paths. Optional `enableLineNumbers` adds `<n>:` line prefixes to compressed/search output.
|
|
153
181
|
|
|
154
182
|
- Count tokens and chars for the given files
|
|
155
183
|
`files-tokens FILE [FILE ...]`
|
|
@@ -158,54 +186,36 @@ TODO: complete with the extension custom tools full documentasions
|
|
|
158
186
|
`files-summarize FILE [FILE ...]`
|
|
159
187
|
|
|
160
188
|
- Generate compressed output for the given files
|
|
161
|
-
`files-compress FILE [FILE ...]`
|
|
189
|
+
`files-compress FILE [FILE ...] [enableLineNumbers]`
|
|
162
190
|
|
|
163
191
|
- Find and extract specific constructs from the given files
|
|
164
|
-
`files-
|
|
192
|
+
`files-search TAG PATTERN FILE [FILE ...] [enableLineNumbers]`
|
|
165
193
|
|
|
166
|
-
- Run static analysis on the given files using
|
|
194
|
+
- Run static analysis on the given files using the checkers configured for their extensions
|
|
167
195
|
`files-static-check FILE [FILE ...]`
|
|
168
196
|
|
|
169
|
-
- Count tokens and chars for canonical docs
|
|
197
|
+
- Count tokens and chars for the canonical docs in the configured `docs-dir` (`REQUIREMENTS.md`, `WORKFLOW.md`, `REFERENCES.md`)
|
|
170
198
|
`tokens`
|
|
171
199
|
|
|
172
|
-
- Generate LLM summary markdown for
|
|
200
|
+
- Generate LLM summary markdown for the configured `src-dir` directories
|
|
173
201
|
`summarize`
|
|
174
202
|
|
|
175
|
-
- Generate
|
|
176
|
-
`
|
|
177
|
-
|
|
178
|
-
- Find and extract specific constructs from source files selected by `git ls-files cached others exclude-standard` under configured `src-dir` directories.
|
|
179
|
-
`find TAG PATTERN`
|
|
180
|
-
|
|
181
|
-
- Run static analysis on source files selected by `git ls-files cached others exclude-standard` under configured `src-dir` directories (plus configured `tests-dir`, excluding `fixtures/`).
|
|
182
|
-
`static-check`
|
|
183
|
-
|
|
184
|
-
- Check repository integrity for the configured git path: clean working tree and valid HEAD.
|
|
185
|
-
`git-check`
|
|
203
|
+
- Generate `REFERENCES.md` from the configured `src-dir` directories and overwrite `<docs-dir>/REFERENCES.md`
|
|
204
|
+
`references`
|
|
186
205
|
|
|
187
|
-
-
|
|
188
|
-
`
|
|
206
|
+
- Generate compressed output for the configured `src-dir` directories
|
|
207
|
+
`compress [enableLineNumbers]`
|
|
189
208
|
|
|
190
|
-
-
|
|
191
|
-
`
|
|
209
|
+
- Find and extract specific constructs from the configured `src-dir` directories
|
|
210
|
+
`search TAG PATTERN [enableLineNumbers]`
|
|
192
211
|
|
|
193
|
-
-
|
|
194
|
-
`
|
|
195
|
-
|
|
196
|
-
- Print the configured `git-path` value from `.req/config.json`; if `.req/config.json` is missing, the command fails with `Error: .req/config.json not found in the project root`.
|
|
197
|
-
`git-path`
|
|
198
|
-
|
|
199
|
-
- Print the configured `base-path` value from `.req/config.json`; if `.req/config.json` is missing, the command fails with `Error: .req/config.json not found in the project root`.
|
|
200
|
-
`get-base-path`
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
- Add `enable-line-numbers` to include `<n>:` prefixes in `files-compress`, `compress`, `files-find`, and `find` output.
|
|
212
|
+
- Run static analysis on the configured `src-dir` plus `tests-dir` selections (excluding `fixtures/`)
|
|
213
|
+
`static-check`
|
|
204
214
|
|
|
205
|
-
|
|
206
|
-
|
|
215
|
+
The set of startup tools active for a project is configurable through `Enable tools` in the
|
|
216
|
+
[settings menu](#settings-menu-pi-usereq).
|
|
207
217
|
|
|
208
|
-
#### Supported
|
|
218
|
+
#### Supported `<TAG>` in search commands
|
|
209
219
|
|
|
210
220
|
- **Python**: CLASS, FUNCTION, DECORATOR, IMPORT, VARIABLE
|
|
211
221
|
- **C**: STRUCT, UNION, ENUM, TYPEDEF, MACRO, FUNCTION, IMPORT, VARIABLE
|
|
@@ -228,39 +238,89 @@ TODO: complete with the extension custom tools full documentasions
|
|
|
228
238
|
- **Zig**: FUNCTION, STRUCT, ENUM, UNION, CONSTANT, VARIABLE, IMPORT
|
|
229
239
|
- **Elixir**: MODULE, FUNCTION, PROTOCOL, IMPL, STRUCT, IMPORT
|
|
230
240
|
|
|
241
|
+
### Standalone CLI
|
|
242
|
+
|
|
243
|
+
The extension ships a standalone CLI entry point (`src/cli.ts`, runnable with `npm run cli -- <options>`) that mirrors the agent tools for scripting and debugging:
|
|
244
|
+
|
|
245
|
+
```text
|
|
246
|
+
--files-tokens FILE [FILE ...]
|
|
247
|
+
--files-summarize FILE [FILE ...]
|
|
248
|
+
--files-compress FILE [FILE ...] [--enable-line-numbers] [--verbose]
|
|
249
|
+
--files-find TAG PATTERN FILE [FILE ...] [--enable-line-numbers] [--verbose]
|
|
250
|
+
--files-static-check FILE [FILE ...]
|
|
251
|
+
--summarize [--verbose]
|
|
252
|
+
--compress [--enable-line-numbers] [--verbose]
|
|
253
|
+
--find TAG PATTERN [--enable-line-numbers] [--verbose]
|
|
254
|
+
--tokens
|
|
255
|
+
--static-check
|
|
256
|
+
--test-static-check {dummy,command} [FILES...]
|
|
257
|
+
--enable-static-check LANG=MODULE[,CMD[,PARAM...]] (repeatable; e.g. --enable-static-check python=command,ruff,check)
|
|
258
|
+
--base <path> # project base for project-scoped commands
|
|
259
|
+
--here # use the current directory as project base
|
|
260
|
+
--verbose
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Project-scoped commands (`--summarize`, `--compress`, `--tokens`, `--find`, `--static-check`) always target the current project configuration and reject `--base` (use `--here`).
|
|
264
|
+
|
|
265
|
+
### Settings Menu (`/pi-usereq`)
|
|
266
|
+
|
|
267
|
+
The interactive configuration menu exposes every user-facing setting; changes are persisted automatically:
|
|
268
|
+
|
|
269
|
+
- **Documentation directory** — `docs-dir` (default `pi-usereq/docs`) used for the canonical documents.
|
|
270
|
+
- **Unit tests directory** — `tests-dir` (default `tests`).
|
|
271
|
+
- **Source directories** — `src-dir` (default `["src"]`) used by the analysis tools.
|
|
272
|
+
- **Context Files** — toggles to inject `REQUIREMENTS.md`, `WORKFLOW.md`, and `REFERENCES.md` into the prompt context through `%%CONTEXT_FILES%%`.
|
|
273
|
+
- **Auto git commit** — `enable` (default) injects git commit instructions into every prompt; `disable` forces read-only git behavior (`git_read-only.md`) and turns worktree orchestration off.
|
|
274
|
+
- **Git worktree** / **Worktree prefix** — enable/disable prompt-command worktree isolation and set the name prefix (default `PI-useReq-`).
|
|
275
|
+
- **Language static code checkers** — per-language `enable`/`disable` flags and the global `Command`-module checker definitions (view/remove/reset with confirmation).
|
|
276
|
+
- **Enable tools** — the subset of configurable startup tools activated for the project (`files-*` and project tools plus the embedded `read`, `bash`, `edit`, `write` quartet).
|
|
277
|
+
- **Notifications** — command-notify, sound, and Pushover settings with per-event routing (completed/interrupted/failed).
|
|
278
|
+
- **Debug** — local debug logging: log file, log-on-status filter, status-change/workflow-event toggles, enabled tools/prompts, and `Enable debug commands for tools`.
|
|
279
|
+
- **Show local/global configuration** — write the exact config file contents into the editor.
|
|
280
|
+
- **Reset defaults** — restore the default configuration with a confirmation preview.
|
|
281
|
+
|
|
231
282
|
### Extension Side Features
|
|
232
283
|
|
|
233
|
-
|
|
284
|
+
#### Sound
|
|
234
285
|
|
|
235
|
-
|
|
286
|
+
The extension plays a bundled sound effect when a prompt ends (completed, interrupted, or failed - each event is independently toggleable).
|
|
236
287
|
|
|
237
|
-
|
|
288
|
+
- Sound levels: `none` → `low` → `mid` → `high`.
|
|
289
|
+
- Default toggle shortcut: `alt+s` (cycles the active runtime level; configurable via `notify-sound-toggle-shortcut`).
|
|
290
|
+
- Each level maps to a configurable shell command; the defaults use `paplay` on the bundled `Soft-high-tech-notification-sound-effect.mp3` with the `%%INSTALLATION_PATH%%` keyword resolved to the installed extension path.
|
|
238
291
|
|
|
239
|
-
|
|
292
|
+
#### Notifications
|
|
240
293
|
|
|
241
|
-
|
|
294
|
+
- **Command notify** — a configurable desktop-notification command (`PI_NOTIFY_CMD`) run when the selected prompt-end events occur.
|
|
295
|
+
- **Pushover** — optional Pushover push notifications (`notify-pushover-*`): user key, API token, priority, title, and text template; disabled until both credentials are set.
|
|
242
296
|
|
|
243
|
-
|
|
297
|
+
#### Status Bar
|
|
244
298
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
-
|
|
252
|
-
- The scripts may modify, create, or remove files in the working tree (files on disk).
|
|
253
|
-
- They do not modify Git history (HEAD), branches, or tags automatically.
|
|
254
|
-
- The index (staging area) and history remain unchanged until the user manually performs staging/commit operations.
|
|
255
|
-
|
|
256
|
-
- How to commit (recommended practice):
|
|
257
|
-
- Review changes generated by the scripts before including them in a commit.
|
|
258
|
-
- Manually add files to commit using `git add <file...>`.
|
|
259
|
-
- Execute the commit with a structured message, for example:
|
|
260
|
-
`git commit -m "change(<COMPONENT>): <SHORT-DESCRIPTION> [<DATE>]"`.
|
|
261
|
-
- Staging and commit operations are under the user's control; the scripts do not perform automatic commits or update Git references.
|
|
262
|
-
|
|
263
|
-
- Practical warnings:
|
|
264
|
-
- Do not use destructive commands (e.g., `git reset --hard`, `git clean -fd`) to "clean" the repository without verifying the impact.
|
|
265
|
-
- If you prefer to isolate changes, execute commands in a branch or a copy of the repository.
|
|
299
|
+
The extension renders a status field in the pi status line showing: extension identity, workflow state (`idle`/`checking`/`running`/`merging`/`error`), current branch, context usage, elapsed run time, and the active sound level.
|
|
300
|
+
|
|
301
|
+
#### Debug
|
|
302
|
+
|
|
303
|
+
- Config-gated slash commands that run the project tools and write the output into the editor: `debug-compress`, `debug-references`, `debug-static-check`, `debug-summarize`, `debug-tokens`.
|
|
304
|
+
- Standalone debug harness: `scripts/pi-usereq-debug.sh` (bash wrapper) and `scripts/debug-extension.ts` with subcommands `inspect`, `session-start`, `command`, `tool`, and `sdk-smoke`.
|
|
305
|
+
- Debug logging writes to the configured `DEBUG_LOG_FILE`, filtered by prompt/tool name and workflow status.
|
|
266
306
|
|
|
307
|
+
|
|
308
|
+
## Note on Git usage
|
|
309
|
+
|
|
310
|
+
This section describes the Git behavior of the `req-*` commands. The commands own their git workflow: validation happens before dispatch and finalization happens at the end of each run.
|
|
311
|
+
|
|
312
|
+
- Required state before execution:
|
|
313
|
+
- The project must be inside a git work tree with a **clean tracked working tree** (`git status --porcelain` empty; the configured debug-log file is ignored by the validation).
|
|
314
|
+
- `HEAD` must resolve. A working branch is recommended: the current branch name is embedded in the generated worktree names (it falls back to `unknown` on a detached `HEAD`).
|
|
315
|
+
- All files must be saved and you must be in the correct project directory.
|
|
316
|
+
|
|
317
|
+
- What the commands do to the repository:
|
|
318
|
+
- Each `req-*` prompt command runs git validation and the required-document checks, then (when `Auto git commit` and `Git worktree` are enabled) creates an isolated **git worktree and branch** named `<GIT_WORKTREE_PREFIX><project>-<branch>-<YYYYMMDDHHMMSS>` (default prefix `PI-useReq-`), switches the session into it, and executes the prompt there.
|
|
319
|
+
- On success the extension restores the original `base-path` session, applies a stash-assisted fast-forward merge of the worktree branch, and deletes the worktree and branch.
|
|
320
|
+
- On failure or interruption the worktree and branch are **kept** and the workflow is parked in the `error` state so the produced artifacts can be inspected or recovered: run `/req-reset` to restore the original base path and force-remove the generated worktrees and branches.
|
|
321
|
+
- With `Auto git commit = enable` (default), every prompt receives structured commit instructions and commits follow the message template `<TYPE>(<COMPONENT>)<BREAKING>: <DESCRIPTION> [useReq]`. With `Auto git commit = disable`, prompts receive a read-only git restriction and worktree orchestration is forced off.
|
|
322
|
+
- The extension never rewrites history and never runs destructive cleanup on your behalf.
|
|
323
|
+
|
|
324
|
+
- Recommended practice:
|
|
325
|
+
- Review the changes produced by each command before pushing them.
|
|
326
|
+
- Do not use destructive commands (e.g., `git reset --hard`, `git clean -fd`) to "clean" the repository without verifying the impact; prefer `/req-reset` for worktree cleanup after failed runs.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-usereq",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.56.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/Ogekuri/PI-useReq.git"
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
]
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
|
-
"@
|
|
36
|
-
"@
|
|
37
|
-
"@
|
|
35
|
+
"@earendil-works/pi-ai": "^0.80.4",
|
|
36
|
+
"@earendil-works/pi-coding-agent": "^0.80.4",
|
|
37
|
+
"@earendil-works/pi-tui": "^0.80.4",
|
|
38
38
|
"@sinclair/typebox": "^0.34.49"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"protobufjs@7.5.4": true,
|
|
57
57
|
"esbuild": true,
|
|
58
58
|
"koffi": true,
|
|
59
|
-
"protobufjs": true
|
|
59
|
+
"protobufjs": true,
|
|
60
|
+
"@google/genai": true
|
|
60
61
|
}
|
|
61
62
|
}
|
package/scripts/lib/sdk-smoke.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file
|
|
3
3
|
* @brief Implements SDK-parity probing and comparison for the standalone debug harness.
|
|
4
|
-
* @details Dynamically loads the official pi SDK when available, inventories extension-owned commands and tools from the runtime surface, normalizes provenance metadata, and compares the result against the offline recorder snapshot. Runtime is O(c + t) in command and
|
|
4
|
+
* @details Dynamically loads the official pi SDK when available, inventories extension-owned commands and tools from the runtime surface, passes the 0.80.4+ `authPath` and `modelsPath` `createAgentSession` options, probes support for the new 0.80.4+ event surface, normalizes provenance metadata, and compares the result against the offline recorder snapshot. Runtime is O(c + t + e) in command, tool, and probed-event counts plus the cost of SDK session creation. Side effects are limited to dynamic module loading, optional SDK-managed filesystem reads, and any extension-owned startup behavior triggered by the official runtime.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import path from "node:path";
|
|
@@ -53,6 +53,7 @@ export interface SdkContractSnapshot {
|
|
|
53
53
|
tools: NormalizedToolRecord[];
|
|
54
54
|
activeTools: string[];
|
|
55
55
|
runtimeShape: string;
|
|
56
|
+
supportedEvents: string[];
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
/**
|
|
@@ -96,6 +97,60 @@ interface SdkApiLike {
|
|
|
96
97
|
getActiveTools?: () => string[];
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
/**
|
|
101
|
+
* @brief Lists the 0.80.4+ pi event names probed for host support by the SDK parity probe.
|
|
102
|
+
* @details The probe registers a no-op handler for each new-event name on the extension runtime and treats a returned unsubscribe function as proof that the host emits the event, mirroring the capability contract used by prompt-end finalization. Lookup complexity is O(1).
|
|
103
|
+
* @satisfies REQ-356
|
|
104
|
+
*/
|
|
105
|
+
const PI_EVENT_SURFACE_PROBE_NAMES = [
|
|
106
|
+
"agent_settled",
|
|
107
|
+
"project_trust",
|
|
108
|
+
"session_info_changed",
|
|
109
|
+
"session_compact_failed",
|
|
110
|
+
"before_provider_headers",
|
|
111
|
+
"after_provider_response",
|
|
112
|
+
"ui_prompt_start",
|
|
113
|
+
"ui_prompt_end",
|
|
114
|
+
"thinking_level_select",
|
|
115
|
+
] as const;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @brief Probes the official SDK runtime for support of the 0.80.4+ pi event surface.
|
|
119
|
+
* @details Locates an object exposing an `on(...)` registration method on the `extensionsResult.runtime` surface, registers a no-op handler for each new-event name, and records those whose registration returns an unsubscribe function (the 0.80.4+ contract). Handlers are unsubscribed immediately after the probe and unsupported or unavailable surfaces yield an empty list. Runtime is O(e) in probed event count. No external state is mutated.
|
|
120
|
+
* @param[in] createAgentSessionResult {unknown} Raw `createAgentSession(...)` result.
|
|
121
|
+
* @return {string[]} Names of supported new pi events, possibly empty.
|
|
122
|
+
* @satisfies REQ-356
|
|
123
|
+
*/
|
|
124
|
+
function probePiEventSurface(createAgentSessionResult: unknown): string[] {
|
|
125
|
+
if (!createAgentSessionResult || typeof createAgentSessionResult !== "object") {
|
|
126
|
+
return [];
|
|
127
|
+
}
|
|
128
|
+
const candidateRoot = createAgentSessionResult as Record<string, unknown>;
|
|
129
|
+
const runtime = (candidateRoot.extensionsResult as Record<string, unknown> | undefined)?.runtime;
|
|
130
|
+
const host = runtime && typeof runtime === "object" ? runtime as Record<string, unknown> : undefined;
|
|
131
|
+
const onMethod = host?.on;
|
|
132
|
+
if (typeof onMethod !== "function") {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
const probeHost = host as unknown as {
|
|
136
|
+
on(event: string, handler: (event: unknown, ctx: unknown) => unknown): unknown;
|
|
137
|
+
};
|
|
138
|
+
const supported: string[] = [];
|
|
139
|
+
for (const eventName of PI_EVENT_SURFACE_PROBE_NAMES) {
|
|
140
|
+
let unsubscribe: unknown;
|
|
141
|
+
try {
|
|
142
|
+
unsubscribe = probeHost.on(eventName, () => undefined);
|
|
143
|
+
} catch {
|
|
144
|
+
unsubscribe = undefined;
|
|
145
|
+
}
|
|
146
|
+
if (typeof unsubscribe === "function") {
|
|
147
|
+
supported.push(eventName);
|
|
148
|
+
(unsubscribe as () => void)();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return supported;
|
|
152
|
+
}
|
|
153
|
+
|
|
99
154
|
/**
|
|
100
155
|
* @brief Normalizes one path relative to the requested project root.
|
|
101
156
|
* @details Converts absolute paths under the project root to slash-normalized relative paths and leaves non-project or pseudo-path values unchanged. Runtime is O(p) in path length. No external state is mutated.
|
|
@@ -414,18 +469,18 @@ export function buildParityReport(offline: OfflineContractSnapshot, sdk: SdkCont
|
|
|
414
469
|
|
|
415
470
|
/**
|
|
416
471
|
* @brief Loads the official pi SDK runtime and extracts the extension-owned command and tool inventories.
|
|
417
|
-
* @details Dynamically imports `@
|
|
472
|
+
* @details Dynamically imports `@earendil-works/pi-coding-agent`, creates a `DefaultResourceLoader` with the requested extension path, creates an SDK session with the 0.80.4+ `authPath` and `modelsPath` options, extracts inventory methods from the returned runtime surface, probes support for the new 0.80.4+ event surface, and filters to extension-owned commands and tools only. Runtime is dominated by SDK startup. Side effects include SDK-managed resource loading and extension startup behavior.
|
|
418
473
|
* @param[in] cwd {string | undefined} Requested working directory.
|
|
419
474
|
* @param[in] extensionPath {string | undefined} Requested extension entry path.
|
|
420
475
|
* @return {Promise<SdkContractSnapshot>} Normalized SDK inventory snapshot.
|
|
421
476
|
* @throws {ReqError} Throws when the SDK package is unavailable, runtime extraction fails, or session creation fails.
|
|
422
|
-
* @satisfies REQ-050, REQ-056, REQ-058
|
|
477
|
+
* @satisfies REQ-050, REQ-056, REQ-058, REQ-356
|
|
423
478
|
*/
|
|
424
479
|
export async function probeSdkRuntime(cwd?: string, extensionPath?: string): Promise<SdkContractSnapshot> {
|
|
425
480
|
const paths = resolveHarnessPaths(cwd, extensionPath);
|
|
426
481
|
let sdkModule: Record<string, unknown>;
|
|
427
482
|
try {
|
|
428
|
-
sdkModule = await import("@
|
|
483
|
+
sdkModule = await import("@earendil-works/pi-coding-agent") as Record<string, unknown>;
|
|
429
484
|
} catch (error) {
|
|
430
485
|
throw new ReqError(`Error: SDK parity loading failed: ${error instanceof Error ? error.message : String(error)}`, 1);
|
|
431
486
|
}
|
|
@@ -454,6 +509,8 @@ export async function probeSdkRuntime(cwd?: string, extensionPath?: string): Pro
|
|
|
454
509
|
resourceLoader,
|
|
455
510
|
sessionManager: SessionManager.inMemory(),
|
|
456
511
|
settingsManager: typeof SettingsManager?.inMemory === "function" ? SettingsManager.inMemory({}) : undefined,
|
|
512
|
+
authPath: path.join(paths.cwd, ".pi-usereq-agent-auth.json"),
|
|
513
|
+
modelsPath: path.join(paths.cwd, ".pi-usereq-agent-models.json"),
|
|
457
514
|
});
|
|
458
515
|
} catch (error) {
|
|
459
516
|
throw new ReqError(`Error: SDK parity loading failed: ${error instanceof Error ? error.message : String(error)}`, 1);
|
|
@@ -485,6 +542,7 @@ export async function probeSdkRuntime(cwd?: string, extensionPath?: string): Pro
|
|
|
485
542
|
tools,
|
|
486
543
|
activeTools,
|
|
487
544
|
runtimeShape: extracted.runtimeShape,
|
|
545
|
+
supportedEvents: probePiEventSurface(createAgentSessionResult),
|
|
488
546
|
};
|
|
489
547
|
}
|
|
490
548
|
|
|
@@ -12,7 +12,7 @@ import type {
|
|
|
12
12
|
ContextUsage,
|
|
13
13
|
ExtensionContext,
|
|
14
14
|
ThemeColor,
|
|
15
|
-
} from "@
|
|
15
|
+
} from "@earendil-works/pi-coding-agent";
|
|
16
16
|
import type { UseReqConfig } from "./config.js";
|
|
17
17
|
import type { PiNotifyOutcome, PiNotifySoundLevel } from "./pi-notify.js";
|
|
18
18
|
import type { PromptCommandExecutionPlan } from "./prompt-command-runtime.js";
|
|
@@ -126,6 +126,7 @@ export interface PiUsereqStatusState {
|
|
|
126
126
|
pendingPromptRequest: PiUsereqPromptRequest | undefined;
|
|
127
127
|
activePromptRequest: PiUsereqPromptRequest | undefined;
|
|
128
128
|
pendingFinalizationOutcome: PiNotifyOutcome | undefined;
|
|
129
|
+
agentSettledEventSupported: boolean | undefined;
|
|
129
130
|
}
|
|
130
131
|
|
|
131
132
|
/**
|
|
@@ -683,6 +684,7 @@ export function createPiUsereqStatusController(): PiUsereqStatusController {
|
|
|
683
684
|
pendingPromptRequest: undefined,
|
|
684
685
|
activePromptRequest: undefined,
|
|
685
686
|
pendingFinalizationOutcome: undefined,
|
|
687
|
+
agentSettledEventSupported: undefined,
|
|
686
688
|
},
|
|
687
689
|
tickHandle: undefined,
|
|
688
690
|
};
|
package/src/core/pi-notify.ts
CHANGED
|
@@ -8,7 +8,7 @@ import os from "node:os";
|
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import { spawn } from "node:child_process";
|
|
10
10
|
import * as https from "node:https";
|
|
11
|
-
import type { AgentEndEvent } from "@
|
|
11
|
+
import type { AgentEndEvent } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import { getInstallationPath, normalizePathSlashes } from "./path-context.js";
|
|
13
13
|
import type { UseReqConfig } from "./config.js";
|
|
14
14
|
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
setRuntimeGitPath,
|
|
29
29
|
setRuntimeWorktreePathState,
|
|
30
30
|
} from "./path-context.js";
|
|
31
|
-
import { SessionManager } from "@
|
|
31
|
+
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
32
32
|
import { resolveRuntimeGitPath } from "./runtime-project-paths.js";
|
|
33
33
|
import {
|
|
34
34
|
clearPersistedPromptCommandSessionContext,
|
|
@@ -1945,11 +1945,11 @@ export async function finalizePromptCommandExecution(
|
|
|
1945
1945
|
/**
|
|
1946
1946
|
* @brief Maps one `agent_end` payload into the canonical prompt-worktree finalization outcome.
|
|
1947
1947
|
* @details Delegates to the shared notification outcome classifier so worktree merge and fork-session retention decisions stay aligned with prompt-end notification routing. Runtime is O(m) in assistant message count. No external state is mutated.
|
|
1948
|
-
* @param[in] event {Pick<import("@
|
|
1948
|
+
* @param[in] event {Pick<import("@earendil-works/pi-coding-agent").AgentEndEvent, "messages">} Agent-end payload subset.
|
|
1949
1949
|
* @return {PiNotifyOutcome} Canonical prompt-end outcome.
|
|
1950
1950
|
*/
|
|
1951
1951
|
export function classifyPromptCommandOutcome(
|
|
1952
|
-
event: Pick<import("@
|
|
1952
|
+
event: Pick<import("@earendil-works/pi-coding-agent").AgentEndEvent, "messages">,
|
|
1953
1953
|
): PiNotifyOutcome {
|
|
1954
1954
|
return classifyPiNotifyOutcome(event);
|
|
1955
1955
|
}
|
package/src/core/prompts.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file
|
|
3
3
|
* @brief Renders bundled pi-usereq prompts for the current project context.
|
|
4
|
-
* @details Applies placeholder substitution, legacy tool-name rewrites, and conditional pi.dev governance guidance before prompt text is sent to the agent. Runtime is linear in prompt size plus replacement count. Side effects are limited to filesystem reads used for
|
|
4
|
+
* @details Applies placeholder substitution, legacy tool-name rewrites, and conditional pi.dev governance guidance before prompt text is sent to the agent. Runtime is linear in prompt size plus replacement count. Side effects are limited to filesystem reads used for the coding-agent-docs directory check and bundled prompt loading.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import fs from "node:fs";
|
|
@@ -61,8 +61,8 @@ const PI_DEV_AWARE_PROMPT_NAMES = new Set<string>([
|
|
|
61
61
|
"refactor",
|
|
62
62
|
]);
|
|
63
63
|
/**
|
|
64
|
-
* @brief Stores the repository-relative pi.dev manifest path used in prompt guidance.
|
|
65
|
-
* @details The constant lets rendered prompts cite the authoritative documentation manifest with a deterministic path. Lookup complexity is O(1).
|
|
64
|
+
* @brief Stores the repository-relative optional pi.dev manifest path used in prompt guidance.
|
|
65
|
+
* @details The constant lets rendered prompts cite the authoritative documentation manifest when present with a deterministic path. The governance block is not gated on this file; it is an optional contract source. Lookup complexity is O(1).
|
|
66
66
|
*/
|
|
67
67
|
const PI_DEV_MANIFEST_PROMPT_PATH = "docs/pi.dev/agent-document-manifest.json";
|
|
68
68
|
/**
|
|
@@ -82,37 +82,36 @@ const PI_DEV_CODING_AGENT_DOCS_PROMPT_PATH = `${PI_DEV_DOCS_PROMPT_PATH}/coding-
|
|
|
82
82
|
const PI_DEV_SOURCE_PROMPT_PATH = "pi.dev-src/pi-mono";
|
|
83
83
|
/**
|
|
84
84
|
* @brief Defines the injected pi.dev governance guidance block.
|
|
85
|
-
* @details The block requires read-only handling for documentation and pi client sources,
|
|
85
|
+
* @details The block requires read-only handling for documentation and pi client sources, coding-agent-document review, coding-agent-document compliance, optional manifest-referenced document handling, and pi client source validation for ambiguous or bug-fix interface work. Construction happens once at module load. Access complexity is O(1).
|
|
86
86
|
* @satisfies REQ-033, REQ-034, REQ-108, REQ-273, REQ-274, REQ-275
|
|
87
87
|
*/
|
|
88
88
|
const PI_DEV_CONFORMANCE_BLOCK = [
|
|
89
89
|
"- Treat every path under `docs/` as read-only; do NOT modify "
|
|
90
|
-
+
|
|
90
|
+
+ "any documentation file, including those under `docs/pi.dev/`.",
|
|
91
91
|
"- Treat every path under `pi.dev-src/` as read-only; do NOT modify "
|
|
92
92
|
+ `\`${PI_DEV_SOURCE_PROMPT_PATH}\` or any other pi client source.`,
|
|
93
93
|
"- If the task creates or modifies software that interfaces with the "
|
|
94
|
-
+ `pi.dev CLI,
|
|
95
|
-
+ "
|
|
96
|
-
|
|
97
|
-
`- Treat \`${PI_DEV_CODING_AGENT_DOCS_PROMPT_PATH}/\` and documents `
|
|
98
|
-
+ `referenced by \`${PI_DEV_MANIFEST_PROMPT_PATH}\` as the `
|
|
94
|
+
+ `pi.dev CLI, review \`${PI_DEV_CODING_AGENT_DOCS_PROMPT_PATH}/\` `
|
|
95
|
+
+ "before analysis, implementation, verification, or bug fixing.",
|
|
96
|
+
`- Treat \`${PI_DEV_CODING_AGENT_DOCS_PROMPT_PATH}/\` as the `
|
|
99
97
|
+ "authoritative read-only interface contract; new or modified "
|
|
100
98
|
+ "pi.dev CLI integrations MUST comply with the APIs they describe.",
|
|
101
|
-
`-
|
|
102
|
-
"- If manifest or "
|
|
103
|
-
+ `\`${PI_DEV_CODING_AGENT_DOCS_PROMPT_PATH}/\` guidance is `
|
|
99
|
+
`- If \`${PI_DEV_CODING_AGENT_DOCS_PROMPT_PATH}/\` guidance is `
|
|
104
100
|
+ "ambiguous for extension-to-pi-client interface behavior, validate "
|
|
105
101
|
+ `the produced source code by analyzing \`${PI_DEV_SOURCE_PROMPT_PATH}\`.`,
|
|
106
102
|
"- For bug fixes or problem resolution influenced by extension-to-pi-client "
|
|
107
103
|
+ "interface implementations, validate the produced source code by "
|
|
108
104
|
+ `analyzing \`${PI_DEV_SOURCE_PROMPT_PATH}\`.`,
|
|
105
|
+
`- If \`${PI_DEV_MANIFEST_PROMPT_PATH}\` exists under \`${PI_DEV_DOCS_PROMPT_PATH}/\`, `
|
|
106
|
+
+ "treat every document path it references as part of the read-only "
|
|
107
|
+
+ "interface contract.",
|
|
109
108
|
].join("\n");
|
|
110
109
|
|
|
111
110
|
/**
|
|
112
111
|
* @brief Builds the conditional pi.dev governance block for one rendered prompt.
|
|
113
|
-
* @details Emits the
|
|
112
|
+
* @details Emits the coding-agent-document-driven governance rules only when the selected bundled prompt can analyze or mutate source code and the project root contains the `docs/pi.dev/coding-agent-docs/` directory; the manifest file is optional and its absence does not suppress the block. Time complexity O(1). No filesystem writes.
|
|
114
113
|
* @param[in] promptName {string} Bundled prompt identifier.
|
|
115
|
-
* @param[in] projectBase {string} Absolute project root used for
|
|
114
|
+
* @param[in] projectBase {string} Absolute project root used for coding-agent-docs directory existence checks.
|
|
116
115
|
* @return {string} Markdown bullet block or the empty string when injection is not applicable.
|
|
117
116
|
* @satisfies REQ-032, REQ-033, REQ-034, REQ-108, REQ-273, REQ-274, REQ-275
|
|
118
117
|
*/
|
|
@@ -120,8 +119,8 @@ function buildPiDevConformanceBlock(promptName: string, projectBase: string): st
|
|
|
120
119
|
if (!PI_DEV_AWARE_PROMPT_NAMES.has(promptName)) {
|
|
121
120
|
return "";
|
|
122
121
|
}
|
|
123
|
-
const
|
|
124
|
-
if (!fs.existsSync(
|
|
122
|
+
const codingAgentDocsPath = path.join(projectBase, PI_DEV_CODING_AGENT_DOCS_PROMPT_PATH);
|
|
123
|
+
if (!fs.existsSync(codingAgentDocsPath) || !fs.statSync(codingAgentDocsPath).isDirectory()) {
|
|
125
124
|
return "";
|
|
126
125
|
}
|
|
127
126
|
return PI_DEV_CONFORMANCE_BLOCK;
|
|
@@ -132,7 +131,7 @@ function buildPiDevConformanceBlock(promptName: string, projectBase: string): st
|
|
|
132
131
|
* @details Inserts the block immediately after the `## Behavior` heading so downstream agents evaluate the rule before workflow steps. Leaves prompts unchanged when no behavior section exists or the block is already present. Time complexity O(n).
|
|
133
132
|
* @param[in] text {string} Prompt markdown after placeholder replacement.
|
|
134
133
|
* @param[in] promptName {string} Bundled prompt identifier.
|
|
135
|
-
* @param[in] projectBase {string} Absolute project root used for
|
|
134
|
+
* @param[in] projectBase {string} Absolute project root used for coding-agent-docs directory existence checks.
|
|
136
135
|
* @return {string} Prompt markdown with zero or one injected conformance block.
|
|
137
136
|
* @satisfies REQ-032, REQ-033, REQ-034, REQ-108, REQ-273, REQ-274, REQ-275
|
|
138
137
|
*/
|
|
@@ -116,6 +116,47 @@ function getGitAddTargetPath(gitRoot: string, absolutePath: string): string {
|
|
|
116
116
|
return relativePath.split(path.sep).join("/");
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
/**
|
|
120
|
+
* @brief Detects whether the git index holds staged differences for the given target paths.
|
|
121
|
+
* @details Executes `git diff --cached --quiet -- <paths>`; exit code `1` signals at least one staged difference, exit code `0` signals no staged difference for the target paths, and any other status or spawn failure is converted into a deterministic `ReqError`. Runtime is dominated by one git subprocess. Side effects include subprocess creation. No index or worktree mutation occurs.
|
|
122
|
+
* @param[in] gitRoot {string} Absolute git root path.
|
|
123
|
+
* @param[in] targetPaths {string[]} Git-add target paths inspected in the cached index.
|
|
124
|
+
* @return {boolean} `true` when at least one staged difference exists for the target paths.
|
|
125
|
+
* @throws {ReqError} Throws when staged-difference inspection fails.
|
|
126
|
+
* @satisfies REQ-357
|
|
127
|
+
*/
|
|
128
|
+
function hasStagedChangesForPaths(gitRoot: string, targetPaths: string[]): boolean {
|
|
129
|
+
const diffResult = runCapture(["git", "diff", "--cached", "--quiet", "--", ...targetPaths], gitRoot);
|
|
130
|
+
if (diffResult.error || diffResult.status === null || diffResult.status < 0 || diffResult.status > 1) {
|
|
131
|
+
throw new ReqError("ERROR: Unable to inspect staged changes before git commit.", 1);
|
|
132
|
+
}
|
|
133
|
+
return diffResult.status === 1;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* @brief Executes one guarded `git commit` invocation for the staged target paths.
|
|
138
|
+
* @details Runs a staged-changes precheck against the cached index and returns without creating a commit when no staged difference exists for the target paths, preventing empty-commit failures such as `nothing to commit, working tree clean`. When a staged difference exists, delegates to `git commit -m <commitMessage>` and converts any non-zero result into a deterministic `ReqError`. Runtime is dominated by up to two git subprocesses. Side effects include subprocess creation and conditional commit creation.
|
|
139
|
+
* @param[in] gitRoot {string} Absolute git root path.
|
|
140
|
+
* @param[in] targetPaths {string[]} Git-add target paths inspected by the staged-changes precheck.
|
|
141
|
+
* @param[in] commitMessage {string} Commit message used when a staged difference exists.
|
|
142
|
+
* @return {void} No return value.
|
|
143
|
+
* @throws {ReqError} Throws when staged-difference inspection or commit creation fails.
|
|
144
|
+
* @satisfies REQ-357, REQ-358
|
|
145
|
+
*/
|
|
146
|
+
function runGuardedGitCommit(gitRoot: string, targetPaths: string[], commitMessage: string): void {
|
|
147
|
+
if (!hasStagedChangesForPaths(gitRoot, targetPaths)) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const commitResult = runCapture(["git", "commit", "-m", commitMessage], gitRoot);
|
|
151
|
+
if (commitResult.error || commitResult.status !== 0) {
|
|
152
|
+
const diagnostic = commitResult.stderr.trim()
|
|
153
|
+
|| commitResult.stdout.trim()
|
|
154
|
+
|| commitResult.error?.message
|
|
155
|
+
|| "unknown error";
|
|
156
|
+
throw new ReqError(`ERROR: git commit failed: ${diagnostic}`, 1);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
119
160
|
/**
|
|
120
161
|
* @brief Prepares the specialized `req-references` execution plan.
|
|
121
162
|
* @details Reuses slash-command-owned git validation, resolves the configured references document path, and returns the fixed commit metadata consumed by the direct-write workflow. Runtime is dominated by git validation subprocesses. Side effects include subprocess creation delegated through `validatePromptGitState(...)`.
|
|
@@ -142,12 +183,12 @@ export function prepareReqReferencesCommandExecution(
|
|
|
142
183
|
|
|
143
184
|
/**
|
|
144
185
|
* @brief Executes the specialized `req-references` direct-write workflow.
|
|
145
|
-
* @details Regenerates `REFERENCES.md` through the same source-summary path used by the `references` tool, stages only the target file, creates the fixed-message commit, and verifies that no residual git-status rows remain after ignored extension-owned debug artifacts are filtered out. Runtime is dominated by summary generation plus
|
|
186
|
+
* @details Regenerates `REFERENCES.md` through the same source-summary path used by the `references` tool, stages only the target file, creates the fixed-message commit through the guarded commit helper whenever a staged difference exists, and verifies that no residual git-status rows remain after ignored extension-owned debug artifacts are filtered out. Runtime is dominated by summary generation plus two to four git subprocesses. Side effects include documentation writes, index mutation, conditional commit creation, and subprocess creation.
|
|
146
187
|
* @param[in] plan {ReqReferencesCommandPlan} Prepared direct-write execution plan.
|
|
147
188
|
* @param[in] config {UseReqConfig} Effective project configuration.
|
|
148
189
|
* @return {void} No return value.
|
|
149
|
-
* @throws {ReqError} Throws when reference generation, staging, commit creation, or cleanliness verification fails.
|
|
150
|
-
* @satisfies REQ-300, REQ-301, REQ-302, REQ-303
|
|
190
|
+
* @throws {ReqError} Throws when reference generation, staging, guarded commit creation, or cleanliness verification fails.
|
|
191
|
+
* @satisfies REQ-300, REQ-301, REQ-302, REQ-303, REQ-357, REQ-358
|
|
151
192
|
*/
|
|
152
193
|
export function executeReqReferencesCommandExecution(
|
|
153
194
|
plan: ReqReferencesCommandPlan,
|
|
@@ -160,14 +201,7 @@ export function executeReqReferencesCommandExecution(
|
|
|
160
201
|
const diagnostic = addResult.stderr.trim() || addResult.error?.message || "unknown error";
|
|
161
202
|
throw new ReqError(`ERROR: git add failed for ${addTargetPath}: ${diagnostic}`, 1);
|
|
162
203
|
}
|
|
163
|
-
|
|
164
|
-
if (commitResult.error || commitResult.status !== 0) {
|
|
165
|
-
const diagnostic = commitResult.stderr.trim()
|
|
166
|
-
|| commitResult.stdout.trim()
|
|
167
|
-
|| commitResult.error?.message
|
|
168
|
-
|| "unknown error";
|
|
169
|
-
throw new ReqError(`ERROR: git commit failed: ${diagnostic}`, 1);
|
|
170
|
-
}
|
|
204
|
+
runGuardedGitCommit(plan.gitPath, [addTargetPath], plan.commitMessage);
|
|
171
205
|
const residualStatusLines = listResidualGitStatusLines(plan.basePath, plan.gitPath, config);
|
|
172
206
|
if (residualStatusLines.length > 0) {
|
|
173
207
|
throw new ReqError("ERROR: Git repository is not clean after req-references commit.", 1);
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* @details Wraps `SettingsList` in one extension-command helper that exposes right-aligned current values, built-in circular scrolling, bottom-line descriptions, and a deterministic bridge for offline test harnesses. Runtime is O(n) in visible choice count plus user interaction cost. Side effects are limited to transient custom-UI rendering.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { getSettingsListTheme, type ThemeColor, type ExtensionCommandContext } from "@
|
|
8
|
-
import { Container, SettingsList, Text, type Component, type SettingItem, type SettingsListTheme } from "@
|
|
7
|
+
import { getSettingsListTheme, type ThemeColor, type ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { Container, SettingsList, Text, type Component, type SettingItem, type SettingsListTheme } from "@earendil-works/pi-tui";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* @brief Describes one selectable pi-usereq settings-menu choice.
|
package/src/index.ts
CHANGED
|
@@ -18,8 +18,8 @@ import type {
|
|
|
18
18
|
ExtensionCommandContext,
|
|
19
19
|
ExtensionContext,
|
|
20
20
|
ToolInfo,
|
|
21
|
-
} from "@
|
|
22
|
-
import { Text } from "@
|
|
21
|
+
} from "@earendil-works/pi-coding-agent";
|
|
22
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
23
23
|
import { Type } from "@sinclair/typebox";
|
|
24
24
|
import {
|
|
25
25
|
buildMonolithicToolExecuteResult,
|
|
@@ -153,6 +153,7 @@ import {
|
|
|
153
153
|
setPiUsereqWorkflowState,
|
|
154
154
|
shouldPreservePromptCommandStateOnShutdown,
|
|
155
155
|
updateExtensionStatus,
|
|
156
|
+
type PiUsereqPromptRequest,
|
|
156
157
|
type PiUsereqStatusController,
|
|
157
158
|
type PiUsereqStatusHookName,
|
|
158
159
|
} from "./core/extension-status.js";
|
|
@@ -1247,9 +1248,138 @@ function applyConfiguredPiUsereqTools(pi: ExtensionAPI, config: UseReqConfig): v
|
|
|
1247
1248
|
pi.setActiveTools(allTools.map((tool) => tool.name).filter((toolName) => nextActive.has(toolName)));
|
|
1248
1249
|
}
|
|
1249
1250
|
|
|
1251
|
+
/**
|
|
1252
|
+
* @brief Probes whether the running pi host emits the `agent_settled` event and caches the result on the status controller state.
|
|
1253
|
+
* @details The 0.80.4+ `ExtensionAPI.on(...)` contract returns an unsubscribe function, and `agent_settled` is introduced in that same release; therefore a registration that returns a callable proves the host supports the event, while a `void` registration means a legacy host that never emits it. The probe registers a no-op handler once per status controller, unsubscribes it when the new contract returns a function, and caches the boolean so all later lookups are O(1). No external state is retained beyond the controller-scoped cached boolean.
|
|
1254
|
+
* @param[in,out] statusController {PiUsereqStatusController} Mutable status controller that caches the probed capability.
|
|
1255
|
+
* @param[in] pi {ExtensionAPI} Active extension API instance used to probe event registration.
|
|
1256
|
+
* @return {boolean} `true` when the host emits `agent_settled`, `false` on legacy hosts.
|
|
1257
|
+
* @satisfies REQ-354, REQ-355
|
|
1258
|
+
*/
|
|
1259
|
+
function isPiAgentSettledEventSupported(
|
|
1260
|
+
statusController: PiUsereqStatusController,
|
|
1261
|
+
pi: ExtensionAPI,
|
|
1262
|
+
): boolean {
|
|
1263
|
+
if (statusController.state.agentSettledEventSupported !== undefined) {
|
|
1264
|
+
return statusController.state.agentSettledEventSupported;
|
|
1265
|
+
}
|
|
1266
|
+
const probeHost = pi as ExtensionAPI & {
|
|
1267
|
+
on(event: string, handler: (event: unknown, ctx: unknown) => unknown): unknown;
|
|
1268
|
+
};
|
|
1269
|
+
const registration = probeHost.on("agent_settled", () => undefined);
|
|
1270
|
+
const supported = typeof registration === "function";
|
|
1271
|
+
if (supported) {
|
|
1272
|
+
(registration as () => void)();
|
|
1273
|
+
}
|
|
1274
|
+
statusController.state.agentSettledEventSupported = supported;
|
|
1275
|
+
return supported;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
/**
|
|
1279
|
+
* @brief Finalizes a matched successful worktree-backed prompt at the current lifecycle point.
|
|
1280
|
+
* @details Executes the deferred stash-assisted merge, transcript preservation, base-path restore, and worktree plus branch deletion through `finalizePromptCommandExecution(...)`, surfaces `error` or warning-only notifications, clears the pending finalization outcome plus prompt state, and transitions workflow state through `merging` to `idle`. Reused by the `agent_settled` handler on 0.80.4+ hosts and directly by the `agent_end` fallback on legacy hosts that never emit `agent_settled`. Runtime is dominated by git finalization. Side effects include branch merges, worktree deletion, notifications, and workflow-state transitions.
|
|
1281
|
+
* @param[in,out] statusController {PiUsereqStatusController} Mutable status controller whose pending and active prompt state is cleared.
|
|
1282
|
+
* @param[in] promptRequest {PiUsereqPromptRequest} Matched successful worktree-backed prompt execution plan.
|
|
1283
|
+
* @param[in] ctx {ExtensionContext} Active extension context used for finalization and notifications.
|
|
1284
|
+
* @return {Promise<void>} Promise resolved when finalization and state transitions complete.
|
|
1285
|
+
* @satisfies REQ-208, REQ-228, REQ-229, REQ-230, REQ-282, REQ-291, REQ-292, REQ-354, REQ-355
|
|
1286
|
+
*/
|
|
1287
|
+
async function finalizeMatchedPromptSuccess(
|
|
1288
|
+
statusController: PiUsereqStatusController,
|
|
1289
|
+
promptRequest: PiUsereqPromptRequest,
|
|
1290
|
+
ctx: ExtensionContext,
|
|
1291
|
+
): Promise<void> {
|
|
1292
|
+
const debugConfig = statusController.config;
|
|
1293
|
+
let promptContext = ctx;
|
|
1294
|
+
let finalization:
|
|
1295
|
+
| {
|
|
1296
|
+
mergeAttempted: boolean;
|
|
1297
|
+
mergeSucceeded: boolean;
|
|
1298
|
+
cleanupSucceeded: boolean;
|
|
1299
|
+
errorMessage?: string;
|
|
1300
|
+
warningMessage?: string;
|
|
1301
|
+
activeContext?: unknown;
|
|
1302
|
+
}
|
|
1303
|
+
| undefined;
|
|
1304
|
+
try {
|
|
1305
|
+
finalization = await finalizePromptCommandExecution(
|
|
1306
|
+
promptRequest,
|
|
1307
|
+
promptContext,
|
|
1308
|
+
debugConfig
|
|
1309
|
+
? { config: debugConfig, workflowState: statusController.state.workflowState }
|
|
1310
|
+
: undefined,
|
|
1311
|
+
);
|
|
1312
|
+
promptContext = (finalization.activeContext ?? promptContext) as typeof ctx;
|
|
1313
|
+
} catch (error) {
|
|
1314
|
+
promptContext = (getPromptCommandErrorContext(error) ?? promptContext) as typeof ctx;
|
|
1315
|
+
let errorMessage = error instanceof Error ? error.message : String(error);
|
|
1316
|
+
let cleanupSucceeded = false;
|
|
1317
|
+
try {
|
|
1318
|
+
promptContext = (await restorePromptCommandExecution(
|
|
1319
|
+
promptRequest,
|
|
1320
|
+
promptContext,
|
|
1321
|
+
debugConfig
|
|
1322
|
+
? { config: debugConfig, workflowState: statusController.state.workflowState }
|
|
1323
|
+
: undefined,
|
|
1324
|
+
) ?? promptContext) as typeof ctx;
|
|
1325
|
+
cleanupSucceeded = true;
|
|
1326
|
+
} catch (restoreError) {
|
|
1327
|
+
promptContext = (getPromptCommandErrorContext(restoreError) ?? promptContext) as typeof ctx;
|
|
1328
|
+
errorMessage = restoreError instanceof Error ? restoreError.message : String(restoreError);
|
|
1329
|
+
}
|
|
1330
|
+
finalization = {
|
|
1331
|
+
mergeAttempted: false,
|
|
1332
|
+
mergeSucceeded: false,
|
|
1333
|
+
cleanupSucceeded,
|
|
1334
|
+
errorMessage,
|
|
1335
|
+
};
|
|
1336
|
+
}
|
|
1337
|
+
if (
|
|
1338
|
+
finalization.errorMessage
|
|
1339
|
+
&& (!finalization.cleanupSucceeded || !finalization.mergeSucceeded)
|
|
1340
|
+
) {
|
|
1341
|
+
if (debugConfig) {
|
|
1342
|
+
transitionPromptWorkflowState(
|
|
1343
|
+
statusController,
|
|
1344
|
+
promptContext,
|
|
1345
|
+
promptRequest.basePath,
|
|
1346
|
+
debugConfig,
|
|
1347
|
+
promptRequest.promptName,
|
|
1348
|
+
"error",
|
|
1349
|
+
);
|
|
1350
|
+
} else {
|
|
1351
|
+
setPiUsereqWorkflowState(statusController, "error", promptContext);
|
|
1352
|
+
}
|
|
1353
|
+
notifyContextSafely(promptContext, finalization.errorMessage, "error");
|
|
1354
|
+
}
|
|
1355
|
+
if (
|
|
1356
|
+
finalization.warningMessage
|
|
1357
|
+
&& finalization.cleanupSucceeded
|
|
1358
|
+
&& finalization.mergeSucceeded
|
|
1359
|
+
&& !finalization.errorMessage
|
|
1360
|
+
) {
|
|
1361
|
+
notifyContextSafely(promptContext, finalization.warningMessage, "info");
|
|
1362
|
+
}
|
|
1363
|
+
statusController.state.pendingFinalizationOutcome = undefined;
|
|
1364
|
+
statusController.state.pendingPromptRequest = undefined;
|
|
1365
|
+
statusController.state.activePromptRequest = undefined;
|
|
1366
|
+
if (debugConfig) {
|
|
1367
|
+
transitionPromptWorkflowState(
|
|
1368
|
+
statusController,
|
|
1369
|
+
promptContext,
|
|
1370
|
+
promptRequest.basePath,
|
|
1371
|
+
debugConfig,
|
|
1372
|
+
promptRequest.promptName,
|
|
1373
|
+
"idle",
|
|
1374
|
+
);
|
|
1375
|
+
} else {
|
|
1376
|
+
setPiUsereqWorkflowState(statusController, "idle", promptContext);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1250
1380
|
/**
|
|
1251
1381
|
* @brief Handles one intercepted pi lifecycle hook for pi-usereq status updates.
|
|
1252
|
-
* @details Applies session-start-specific resource validation, project-config refresh, startup-tool enablement, and selected debug-tool logging before forwarding the originating hook name and payload into the shared `updateExtensionStatus(...)` pipeline. Before `agent_start`, re-verifies any prepared prompt execution session switch. On `agent_end`, dispatches configured command-notify, sound, and prompt-specific Pushover effects, logs dedicated workflow-closure diagnostics, classifies the prompt outcome, and for every matched successful worktree-backed completion defers the restore switch, stash-assisted merge, and worktree deletion to `agent_settled`
|
|
1382
|
+
* @details Applies session-start-specific resource validation, project-config refresh, startup-tool enablement, and selected debug-tool logging before forwarding the originating hook name and payload into the shared `updateExtensionStatus(...)` pipeline. Before `agent_start`, re-verifies any prepared prompt execution session switch. On `agent_end`, dispatches configured command-notify, sound, and prompt-specific Pushover effects, logs dedicated workflow-closure diagnostics, classifies the prompt outcome, and for every matched successful worktree-backed completion defers the restore switch, stash-assisted merge, and worktree deletion to `agent_settled` when the running pi host supports that 0.80.4+ event (because its `switchSession` awaits the active agent run to become idle and would deadlock inside `agent_end`), or executes the finalization directly at `agent_end` when the host does not emit `agent_settled`. On `agent_settled`, reuses persisted replacement-session command contexts when event contexts omit `switchSession()`, executes the deferred stash-assisted merge-and-delete finalization path, emits a warning-only notification when restored `base-path` changes are reapplied after merge, tolerates stale replacement-session notification contexts after session replacement, retains the worktree plus notifies closure failure for interrupted or failed outcomes, logs selected prompt workflow transitions, and transitions workflow state through `merging`, `error`, and `idle` as required. On `session_shutdown`, captures pre-update prompt snapshots so workflow-shutdown diagnostics and same-runtime command continuation preserve the active prompt workflow state across switch-triggered rebinding, then disposes the shared controller. Runtime is dominated by configuration loading during `session_start` and git finalization during matched successful closure handling; all other hooks are O(1). Side effects include resource checks, active-tool mutation, active-session replacement, status updates, live-ticker disposal on shutdown, optional child-process spawning, outbound HTTPS requests, branch merges, worktree deletion, and optional debug-log writes.
|
|
1253
1383
|
* @param[in] pi {ExtensionAPI} Active extension API instance.
|
|
1254
1384
|
* @param[in,out] statusController {PiUsereqStatusController} Mutable status controller.
|
|
1255
1385
|
* @param[in] hookName {PiUsereqStatusHookName} Intercepted hook name.
|
|
@@ -1373,24 +1503,36 @@ async function handleExtensionStatusEvent(
|
|
|
1373
1503
|
);
|
|
1374
1504
|
}
|
|
1375
1505
|
if (shouldFinalizeMatchedSuccess) {
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1506
|
+
if (isPiAgentSettledEventSupported(statusController, pi)) {
|
|
1507
|
+
// Defer the restore switch, merge, and worktree deletion to
|
|
1508
|
+
// `agent_settled`. The pi 0.80.4+ `switchSession` implementation
|
|
1509
|
+
// awaits the active agent run to become idle before replacing the
|
|
1510
|
+
// session, and that idle transition only happens at `agent_settled`.
|
|
1511
|
+
// Calling `switchSession` here would deadlock the `agent_end` handler
|
|
1512
|
+
// and leave the workflow parked in `merging` forever.
|
|
1513
|
+
statusController.state.pendingFinalizationOutcome = outcome;
|
|
1514
|
+
if (debugConfig) {
|
|
1515
|
+
transitionPromptWorkflowState(
|
|
1516
|
+
statusController,
|
|
1517
|
+
promptContext,
|
|
1518
|
+
activePromptRequest.basePath,
|
|
1519
|
+
debugConfig,
|
|
1520
|
+
activePromptRequest.promptName,
|
|
1521
|
+
"merging",
|
|
1522
|
+
);
|
|
1523
|
+
} else {
|
|
1524
|
+
setPiUsereqWorkflowState(statusController, "merging", promptContext);
|
|
1525
|
+
}
|
|
1526
|
+
} else {
|
|
1527
|
+
// Legacy hosts never emit `agent_settled`, so no idle-waiting
|
|
1528
|
+
// `switchSession` deadlock exists; finalize the matched success
|
|
1529
|
+
// directly at `agent_end` to avoid parking in `merging` forever.
|
|
1530
|
+
statusController.state.pendingFinalizationOutcome = outcome;
|
|
1531
|
+
await finalizeMatchedPromptSuccess(
|
|
1385
1532
|
statusController,
|
|
1533
|
+
activePromptRequest,
|
|
1386
1534
|
promptContext,
|
|
1387
|
-
activePromptRequest.basePath,
|
|
1388
|
-
debugConfig,
|
|
1389
|
-
activePromptRequest.promptName,
|
|
1390
|
-
"merging",
|
|
1391
1535
|
);
|
|
1392
|
-
} else {
|
|
1393
|
-
setPiUsereqWorkflowState(statusController, "merging", promptContext);
|
|
1394
1536
|
}
|
|
1395
1537
|
} else if (closureFailureMessage !== undefined) {
|
|
1396
1538
|
// Worktree-backed run that ended interrupted, failed, aborted, or
|
|
@@ -1464,92 +1606,7 @@ async function handleExtensionStatusEvent(
|
|
|
1464
1606
|
&& settledPromptRequest.worktreeDir !== undefined
|
|
1465
1607
|
&& pendingOutcome === "completed"
|
|
1466
1608
|
) {
|
|
1467
|
-
|
|
1468
|
-
let promptContext = ctx;
|
|
1469
|
-
let finalization:
|
|
1470
|
-
| {
|
|
1471
|
-
mergeAttempted: boolean;
|
|
1472
|
-
mergeSucceeded: boolean;
|
|
1473
|
-
cleanupSucceeded: boolean;
|
|
1474
|
-
errorMessage?: string;
|
|
1475
|
-
warningMessage?: string;
|
|
1476
|
-
activeContext?: unknown;
|
|
1477
|
-
}
|
|
1478
|
-
| undefined;
|
|
1479
|
-
try {
|
|
1480
|
-
finalization = await finalizePromptCommandExecution(
|
|
1481
|
-
settledPromptRequest,
|
|
1482
|
-
promptContext,
|
|
1483
|
-
debugConfig
|
|
1484
|
-
? { config: debugConfig, workflowState: statusController.state.workflowState }
|
|
1485
|
-
: undefined,
|
|
1486
|
-
);
|
|
1487
|
-
promptContext = (finalization.activeContext ?? promptContext) as typeof ctx;
|
|
1488
|
-
} catch (error) {
|
|
1489
|
-
promptContext = (getPromptCommandErrorContext(error) ?? promptContext) as typeof ctx;
|
|
1490
|
-
let errorMessage = error instanceof Error ? error.message : String(error);
|
|
1491
|
-
let cleanupSucceeded = false;
|
|
1492
|
-
try {
|
|
1493
|
-
promptContext = (await restorePromptCommandExecution(
|
|
1494
|
-
settledPromptRequest,
|
|
1495
|
-
promptContext,
|
|
1496
|
-
debugConfig
|
|
1497
|
-
? { config: debugConfig, workflowState: statusController.state.workflowState }
|
|
1498
|
-
: undefined,
|
|
1499
|
-
) ?? promptContext) as typeof ctx;
|
|
1500
|
-
cleanupSucceeded = true;
|
|
1501
|
-
} catch (restoreError) {
|
|
1502
|
-
promptContext = (getPromptCommandErrorContext(restoreError) ?? promptContext) as typeof ctx;
|
|
1503
|
-
errorMessage = restoreError instanceof Error ? restoreError.message : String(restoreError);
|
|
1504
|
-
}
|
|
1505
|
-
finalization = {
|
|
1506
|
-
mergeAttempted: false,
|
|
1507
|
-
mergeSucceeded: false,
|
|
1508
|
-
cleanupSucceeded,
|
|
1509
|
-
errorMessage,
|
|
1510
|
-
};
|
|
1511
|
-
}
|
|
1512
|
-
if (
|
|
1513
|
-
finalization.errorMessage
|
|
1514
|
-
&& (!finalization.cleanupSucceeded || !finalization.mergeSucceeded)
|
|
1515
|
-
) {
|
|
1516
|
-
if (debugConfig) {
|
|
1517
|
-
transitionPromptWorkflowState(
|
|
1518
|
-
statusController,
|
|
1519
|
-
promptContext,
|
|
1520
|
-
settledPromptRequest.basePath,
|
|
1521
|
-
debugConfig,
|
|
1522
|
-
settledPromptRequest.promptName,
|
|
1523
|
-
"error",
|
|
1524
|
-
);
|
|
1525
|
-
} else {
|
|
1526
|
-
setPiUsereqWorkflowState(statusController, "error", promptContext);
|
|
1527
|
-
}
|
|
1528
|
-
notifyContextSafely(promptContext, finalization.errorMessage, "error");
|
|
1529
|
-
}
|
|
1530
|
-
if (
|
|
1531
|
-
finalization.warningMessage
|
|
1532
|
-
&& finalization.cleanupSucceeded
|
|
1533
|
-
&& finalization.mergeSucceeded
|
|
1534
|
-
&& !finalization.errorMessage
|
|
1535
|
-
) {
|
|
1536
|
-
notifyContextSafely(promptContext, finalization.warningMessage, "info");
|
|
1537
|
-
}
|
|
1538
|
-
statusController.state.pendingFinalizationOutcome = undefined;
|
|
1539
|
-
statusController.state.pendingPromptRequest = undefined;
|
|
1540
|
-
statusController.state.activePromptRequest = undefined;
|
|
1541
|
-
if (debugConfig) {
|
|
1542
|
-
transitionPromptWorkflowState(
|
|
1543
|
-
statusController,
|
|
1544
|
-
promptContext,
|
|
1545
|
-
settledPromptRequest.basePath,
|
|
1546
|
-
debugConfig,
|
|
1547
|
-
settledPromptRequest.promptName,
|
|
1548
|
-
"idle",
|
|
1549
|
-
);
|
|
1550
|
-
} else {
|
|
1551
|
-
setPiUsereqWorkflowState(statusController, "idle", promptContext);
|
|
1552
|
-
}
|
|
1609
|
+
await finalizeMatchedPromptSuccess(statusController, settledPromptRequest, ctx);
|
|
1553
1610
|
}
|
|
1554
1611
|
}
|
|
1555
1612
|
if (hookName === "session_shutdown") {
|