implannotator 0.2.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/CHANGELOG.md +19 -0
- package/LICENSE +21 -0
- package/README.md +76 -0
- package/THIRD_PARTY_NOTICES.md +9 -0
- package/docs/architecture.md +12 -0
- package/docs/compatibility.md +9 -0
- package/docs/configuration.md +25 -0
- package/docs/installation.md +30 -0
- package/docs/qa.md +5 -0
- package/docs/security.md +10 -0
- package/docs/troubleshooting.md +17 -0
- package/docs/workflow.md +12 -0
- package/extensions/commands/register.ts +39 -0
- package/extensions/config/defaults.ts +16 -0
- package/extensions/config/loader.ts +84 -0
- package/extensions/domain/types.ts +94 -0
- package/extensions/index.ts +44 -0
- package/extensions/plannotator/availability.ts +13 -0
- package/extensions/plannotator/client.ts +54 -0
- package/extensions/plannotator/contracts.ts +20 -0
- package/extensions/policy/change-inventory.ts +23 -0
- package/extensions/policy/mutation-gate.ts +40 -0
- package/extensions/policy/non-git-diff.ts +29 -0
- package/extensions/qa/checklist.ts +31 -0
- package/extensions/qa/classifier.ts +23 -0
- package/extensions/review/final-report.ts +6 -0
- package/extensions/state/atomic-file.ts +14 -0
- package/extensions/state/redaction.ts +19 -0
- package/extensions/state/run-layout.ts +40 -0
- package/extensions/state/run-store.ts +97 -0
- package/extensions/tools/control-tool.ts +95 -0
- package/extensions/workflow/approval-policy.ts +21 -0
- package/extensions/workflow/orchestrator.ts +290 -0
- package/extensions/workflow/transitions.ts +26 -0
- package/package.json +77 -0
- package/schemas/config.schema.json +63 -0
- package/schemas/decision.schema.json +16 -0
- package/schemas/qa-report.schema.json +14 -0
- package/schemas/run.schema.json +15 -0
- package/scripts/assert-publish-ready.mjs +6 -0
- package/scripts/check-plannotator-compat.mjs +23 -0
- package/scripts/update-check.mjs +32 -0
- package/scripts/verify-implannotator-snapshot.mjs +24 -0
- package/skills/implannotator/SKILL.md +194 -0
- package/skills/implannotator/agents/implannotator_asset_producer.toml +92 -0
- package/skills/implannotator/agents/implannotator_manual_edit_applier.toml +95 -0
- package/skills/implannotator/agents/openai.yaml +4 -0
- package/skills/implannotator/reference/adapt.md +311 -0
- package/skills/implannotator/reference/animate.md +201 -0
- package/skills/implannotator/reference/approval.md +26 -0
- package/skills/implannotator/reference/audit.md +133 -0
- package/skills/implannotator/reference/bolder.md +113 -0
- package/skills/implannotator/reference/brand.md +108 -0
- package/skills/implannotator/reference/clarify.md +288 -0
- package/skills/implannotator/reference/codex.md +105 -0
- package/skills/implannotator/reference/colorize.md +257 -0
- package/skills/implannotator/reference/craft.md +123 -0
- package/skills/implannotator/reference/critique.md +790 -0
- package/skills/implannotator/reference/delight.md +302 -0
- package/skills/implannotator/reference/distill.md +111 -0
- package/skills/implannotator/reference/document.md +429 -0
- package/skills/implannotator/reference/extract.md +69 -0
- package/skills/implannotator/reference/harden.md +347 -0
- package/skills/implannotator/reference/hooks.md +90 -0
- package/skills/implannotator/reference/implannotator-workflow.md +65 -0
- package/skills/implannotator/reference/init.md +172 -0
- package/skills/implannotator/reference/interaction-design.md +189 -0
- package/skills/implannotator/reference/layout.md +161 -0
- package/skills/implannotator/reference/live.md +720 -0
- package/skills/implannotator/reference/onboard.md +234 -0
- package/skills/implannotator/reference/optimize.md +258 -0
- package/skills/implannotator/reference/overdrive.md +130 -0
- package/skills/implannotator/reference/polish.md +241 -0
- package/skills/implannotator/reference/product.md +60 -0
- package/skills/implannotator/reference/qa.md +40 -0
- package/skills/implannotator/reference/quieter.md +99 -0
- package/skills/implannotator/reference/review-repair.md +10 -0
- package/skills/implannotator/reference/shape.md +165 -0
- package/skills/implannotator/reference/typeset.md +279 -0
- package/skills/implannotator/scripts/command-metadata.json +94 -0
- package/skills/implannotator/scripts/context-signals.mjs +225 -0
- package/skills/implannotator/scripts/context.mjs +280 -0
- package/skills/implannotator/scripts/critique-storage.mjs +242 -0
- package/skills/implannotator/scripts/detect-csp.mjs +198 -0
- package/skills/implannotator/scripts/detect.mjs +21 -0
- package/skills/implannotator/scripts/detector/browser/injected/index.mjs +1937 -0
- package/skills/implannotator/scripts/detector/cli/main.mjs +268 -0
- package/skills/implannotator/scripts/detector/design-system.mjs +750 -0
- package/skills/implannotator/scripts/detector/detect-antipatterns-browser.js +5138 -0
- package/skills/implannotator/scripts/detector/detect-antipatterns.mjs +50 -0
- package/skills/implannotator/scripts/detector/engines/browser/detect-url.mjs +277 -0
- package/skills/implannotator/scripts/detector/engines/regex/detect-text.mjs +564 -0
- package/skills/implannotator/scripts/detector/engines/static-html/css-cascade.mjs +1015 -0
- package/skills/implannotator/scripts/detector/engines/static-html/detect-html.mjs +229 -0
- package/skills/implannotator/scripts/detector/engines/visual/screenshot-contrast.mjs +189 -0
- package/skills/implannotator/scripts/detector/findings.mjs +12 -0
- package/skills/implannotator/scripts/detector/node/file-system.mjs +198 -0
- package/skills/implannotator/scripts/detector/profile/profiler.mjs +166 -0
- package/skills/implannotator/scripts/detector/registry/antipatterns.mjs +448 -0
- package/skills/implannotator/scripts/detector/rules/checks.mjs +2671 -0
- package/skills/implannotator/scripts/detector/shared/color.mjs +124 -0
- package/skills/implannotator/scripts/detector/shared/constants.mjs +101 -0
- package/skills/implannotator/scripts/detector/shared/page.mjs +7 -0
- package/skills/implannotator/scripts/hook-admin.mjs +636 -0
- package/skills/implannotator/scripts/hook-before-edit.mjs +476 -0
- package/skills/implannotator/scripts/hook-lib.mjs +1526 -0
- package/skills/implannotator/scripts/hook.mjs +61 -0
- package/skills/implannotator/scripts/lib/design-parser.mjs +842 -0
- package/skills/implannotator/scripts/lib/implannotator-config.mjs +638 -0
- package/skills/implannotator/scripts/lib/implannotator-paths.mjs +126 -0
- package/skills/implannotator/scripts/lib/is-generated.mjs +69 -0
- package/skills/implannotator/scripts/live/browser-script-parts.mjs +49 -0
- package/skills/implannotator/scripts/live/completion.mjs +19 -0
- package/skills/implannotator/scripts/live/event-validation.mjs +137 -0
- package/skills/implannotator/scripts/live/insert-ui.mjs +458 -0
- package/skills/implannotator/scripts/live/manual-apply.mjs +939 -0
- package/skills/implannotator/scripts/live/manual-edit-routes.mjs +357 -0
- package/skills/implannotator/scripts/live/manual-edits-buffer.mjs +152 -0
- package/skills/implannotator/scripts/live/session-store.mjs +289 -0
- package/skills/implannotator/scripts/live/svelte-component.mjs +826 -0
- package/skills/implannotator/scripts/live/sveltekit-adapter.mjs +274 -0
- package/skills/implannotator/scripts/live/ui-core.mjs +180 -0
- package/skills/implannotator/scripts/live/vocabulary.mjs +36 -0
- package/skills/implannotator/scripts/live-accept.mjs +812 -0
- package/skills/implannotator/scripts/live-browser-dom.js +146 -0
- package/skills/implannotator/scripts/live-browser-session.js +123 -0
- package/skills/implannotator/scripts/live-browser.js +11161 -0
- package/skills/implannotator/scripts/live-commit-manual-edits.mjs +1241 -0
- package/skills/implannotator/scripts/live-complete.mjs +75 -0
- package/skills/implannotator/scripts/live-copy-edit-agent.mjs +683 -0
- package/skills/implannotator/scripts/live-discard-manual-edits.mjs +51 -0
- package/skills/implannotator/scripts/live-inject.mjs +583 -0
- package/skills/implannotator/scripts/live-insert.mjs +272 -0
- package/skills/implannotator/scripts/live-manual-edit-evidence.mjs +363 -0
- package/skills/implannotator/scripts/live-poll.mjs +379 -0
- package/skills/implannotator/scripts/live-resume.mjs +94 -0
- package/skills/implannotator/scripts/live-server.mjs +1134 -0
- package/skills/implannotator/scripts/live-status.mjs +61 -0
- package/skills/implannotator/scripts/live-wrap.mjs +894 -0
- package/skills/implannotator/scripts/live.mjs +246 -0
- package/skills/implannotator/scripts/modern-screenshot.umd.js +14 -0
- package/skills/implannotator/scripts/palette.mjs +633 -0
- package/skills/implannotator/scripts/pin.mjs +214 -0
- package/skills/implannotator/vendor/impeccable-manifest.json +680 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0 - Unreleased
|
|
4
|
+
|
|
5
|
+
- Move the canonical npm package from `@yoseph_23/implannotator` to the unscoped `implannotator` name.
|
|
6
|
+
- Support the shorter installation command: `pi install npm:implannotator`.
|
|
7
|
+
- Add Pi extension and skill discovery keywords for the package gallery.
|
|
8
|
+
|
|
9
|
+
## 0.1.1 - 2026-07-15
|
|
10
|
+
|
|
11
|
+
- Reuse `@plannotator/pi-extension` as a separately installed companion package.
|
|
12
|
+
- Stop bundling and statically loading a second Plannotator extension, preventing duplicate tool, command, and `--plan` flag conflicts.
|
|
13
|
+
- Detect companion availability at session startup and retain text approval fallback when it is missing.
|
|
14
|
+
- Reduce the published package size substantially.
|
|
15
|
+
|
|
16
|
+
## 0.1.0 - 2026-07-15
|
|
17
|
+
|
|
18
|
+
- Initial independent Implannotator Pi package.
|
|
19
|
+
- Approval-gated planning, adaptive QA, Plannotator code review, bounded repair loops, resumable audit records, and complete namespaced design guidance.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 yoseph_23
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Implannotator
|
|
2
|
+
|
|
3
|
+
`implannotator` is a cross-platform Pi package for production-grade frontend work with mandatory plan approval, adaptive browser QA, Plannotator code review, bounded repairs, and resumable audit records.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Plannotator is a companion Pi package. Install it once, then install Implannotator:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pi install npm:@plannotator/pi-extension
|
|
11
|
+
pi install npm:implannotator
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Implannotator reuses the active companion through Plannotator's public event API; it does not bundle or register a second Plannotator extension. If the companion is unavailable, mandatory text approval remains available.
|
|
15
|
+
|
|
16
|
+
For local development:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
cd <path-to-repository>/implannotator
|
|
20
|
+
npm install
|
|
21
|
+
pi install npm:@plannotator/pi-extension
|
|
22
|
+
pi install .
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Restart Pi or run `/reload`, then use:
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
/implannotator
|
|
29
|
+
/implannotator craft <target>
|
|
30
|
+
/implannotator audit <target>
|
|
31
|
+
/implannotator status
|
|
32
|
+
/implannotator resume
|
|
33
|
+
/implannotator review
|
|
34
|
+
/implannotator verify
|
|
35
|
+
/implannotator config
|
|
36
|
+
/implannotator update-check
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The skill may also be selected automatically for frontend design and implementation requests.
|
|
40
|
+
|
|
41
|
+
## Workflow
|
|
42
|
+
|
|
43
|
+
1. Inspect the project without mutation.
|
|
44
|
+
2. Draft a complete plan.
|
|
45
|
+
3. Approve it in Plannotator or through the text fallback.
|
|
46
|
+
4. Implement as the sole writer.
|
|
47
|
+
5. Run adaptive Quick, Standard, or Full QA.
|
|
48
|
+
6. Open the initial code review.
|
|
49
|
+
7. Repair up to three times without reopening intermediate browser tabs.
|
|
50
|
+
8. Open one final review.
|
|
51
|
+
9. Complete only after approval or an explicit warned override.
|
|
52
|
+
|
|
53
|
+
Audit records are written to `.implannotator/runs/<run-id>/`. The package never automatically stashes, resets, commits, or pushes.
|
|
54
|
+
|
|
55
|
+
## Configuration
|
|
56
|
+
|
|
57
|
+
Configuration merges in this order:
|
|
58
|
+
|
|
59
|
+
1. Built-in defaults
|
|
60
|
+
2. `~/.pi/agent/implannotator.json`
|
|
61
|
+
3. `<project>/.pi/implannotator.json`
|
|
62
|
+
|
|
63
|
+
Low-risk automatic approval is disabled by default. See [docs/configuration.md](docs/configuration.md).
|
|
64
|
+
|
|
65
|
+
## Development
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm run typecheck
|
|
69
|
+
npm run lint
|
|
70
|
+
npm test
|
|
71
|
+
npm run test:compat
|
|
72
|
+
npm run verify:snapshot
|
|
73
|
+
npm run pack:check
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
See [docs/installation.md](docs/installation.md), [docs/architecture.md](docs/architecture.md), [docs/workflow.md](docs/workflow.md), [docs/qa.md](docs/qa.md), and [docs/security.md](docs/security.md).
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
## Plannotator
|
|
4
|
+
|
|
5
|
+
Implannotator interoperates with the separately installed `@plannotator/pi-extension` companion package, licensed under MIT OR Apache-2.0. See <https://github.com/backnotprop/plannotator>.
|
|
6
|
+
|
|
7
|
+
## Implannotator design-guidance snapshot
|
|
8
|
+
|
|
9
|
+
The files under `skills/implannotator/` were adapted from the locally installed Impeccable skill version 3.7.1. The source snapshot did not include license or repository metadata. **Do not publish this npm package publicly until redistribution rights and required attribution have been confirmed.** `scripts/assert-publish-ready.mjs` enforces an explicit acknowledgement.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
One Pi package exposes two resources:
|
|
4
|
+
|
|
5
|
+
- `skills/implannotator/`: the complete namespaced design skill and mandatory workflow contract.
|
|
6
|
+
- `extensions/index.ts`: commands, control tool, approval gate, state machine, audit storage, QA contracts, and Plannotator adapter.
|
|
7
|
+
|
|
8
|
+
The package reuses a separately installed `@plannotator/pi-extension` companion and communicates only through its documented `plannotator:request` event channel. It neither bundles nor statically loads a second Plannotator extension, and it does not import private browser/server modules. Session startup detects the companion's public tool/command registration; when absent, the workflow uses mandatory text approval fallback.
|
|
9
|
+
|
|
10
|
+
State is persisted atomically under `.implannotator/runs/<run-id>/`. The workflow is resumable and illegal state transitions are rejected.
|
|
11
|
+
|
|
12
|
+
The extension enforces pre-approval mutation blocking for built-in write/edit/shell tools. The skill requires the main agent to be the sole writer; optional subagents are read-only reviewers.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Compatibility
|
|
2
|
+
|
|
3
|
+
- Node.js 20 or newer
|
|
4
|
+
- Pi 0.80.7 or newer (security baseline)
|
|
5
|
+
- Companion Pi package `@plannotator/pi-extension` compatible with `^0.23.1`
|
|
6
|
+
|
|
7
|
+
`npm run test:compat` checks the installed Plannotator public event contract. `npm run update-check` reports newer releases without modifying dependencies. CI tests supported Node versions on Linux, macOS, and Windows.
|
|
8
|
+
|
|
9
|
+
Install the companion once with `pi install npm:@plannotator/pi-extension`. Implannotator does not bundle or register another copy, so it can coexist with the standalone extension without duplicate tools, commands, flags, listeners, or tabs. If the companion is disabled or missing, Implannotator reports that state and uses text approval fallback.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
Create `~/.pi/agent/implannotator.json` for global defaults or `.pi/implannotator.json` in a project for overrides.
|
|
4
|
+
|
|
5
|
+
```json
|
|
6
|
+
{
|
|
7
|
+
"schemaVersion": 1,
|
|
8
|
+
"approval": {
|
|
9
|
+
"required": true,
|
|
10
|
+
"allowLowRiskAutoApprove": false,
|
|
11
|
+
"proceedRequiresSecondConfirmation": true,
|
|
12
|
+
"browserTimeoutMs": 1800000
|
|
13
|
+
},
|
|
14
|
+
"repairs": {
|
|
15
|
+
"maxAttempts": 3,
|
|
16
|
+
"reopenOnlyForFinalReview": true
|
|
17
|
+
},
|
|
18
|
+
"qa": {
|
|
19
|
+
"defaultProfile": "adaptive",
|
|
20
|
+
"requireEvidence": true
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Mandatory approval, second-confirmation overrides, safe Git behavior, the three-repair ceiling, final-only tab reopening, read-only subagents, and sole-writer behavior cannot be weakened by configuration.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Installation
|
|
2
|
+
|
|
3
|
+
Implannotator and Plannotator are separate Pi packages that cooperate through Plannotator's public event API.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pi install npm:@plannotator/pi-extension
|
|
7
|
+
pi install npm:implannotator
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Restart Pi or run `/reload`. Use `/implannotator` to begin.
|
|
11
|
+
|
|
12
|
+
## Why two packages?
|
|
13
|
+
|
|
14
|
+
Pi loads separately installed packages from independent module roots. Bundling and statically loading Plannotator inside Implannotator while also installing Plannotator directly registers the same tools, commands, and `--plan` flag twice. Implannotator reuses the one active companion and never loads a duplicate.
|
|
15
|
+
|
|
16
|
+
## Without Plannotator
|
|
17
|
+
|
|
18
|
+
Implannotator remains usable with mandatory text approval. It shows a session warning and status indicator until the companion is installed or enabled.
|
|
19
|
+
|
|
20
|
+
## Migrate from the scoped package
|
|
21
|
+
|
|
22
|
+
The original releases used `@yoseph_23/implannotator`. Version-pinned Pi packages do not update automatically, and npm treats the unscoped name as a separate package. Remove the scoped installation and install the canonical unscoped package:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pi remove npm:@yoseph_23/implannotator@0.1.1
|
|
26
|
+
pi install npm:@plannotator/pi-extension
|
|
27
|
+
pi install npm:implannotator
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
If a different scoped version is installed, use the exact source shown by `pi list` in the remove command.
|
package/docs/qa.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# QA profiles
|
|
2
|
+
|
|
3
|
+
Quick covers targeted checks for tiny isolated changes. Standard adds multi-viewport, state, theme, motion, touch, overflow, and content-resilience checks. Full adds reference comparison, Core Web Vitals, cross-browser coverage when available, internationalization, full user flows, and independent read-only review.
|
|
4
|
+
|
|
5
|
+
Each check is recorded as passed, failed, unavailable, or not-applicable. Unavailable tooling is never reported as passed. Every failure links to command, screenshot, URL/viewport, or observation evidence.
|
package/docs/security.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Security and work preservation
|
|
2
|
+
|
|
3
|
+
- Pi packages execute with the user's permissions; inspect package source before installation.
|
|
4
|
+
- Project mutation is blocked until plan approval during an active run.
|
|
5
|
+
- Writes outside the project are blocked during active runs.
|
|
6
|
+
- Automatic Git stash, reset, clean, restore, checkout, switch, commit, merge, rebase, and push are blocked.
|
|
7
|
+
- Existing dirty work is preserved and reported.
|
|
8
|
+
- Audit records and screenshots may contain private product information. They use restrictive local file permissions where supported and should not be committed unless deliberately reviewed.
|
|
9
|
+
- `allowLowRiskAutoApprove` is opt-in and applies only when interactive approval is unavailable.
|
|
10
|
+
- Public npm publishing is blocked until third-party redistribution rights are acknowledged explicitly.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
## Browser review unavailable
|
|
4
|
+
|
|
5
|
+
Implannotator returns a text-review request. Present the plan or diff evidence in chat and record the explicit decision. Low-risk continuation is not automatic unless enabled.
|
|
6
|
+
|
|
7
|
+
## Plannotator companion not active
|
|
8
|
+
|
|
9
|
+
Install or enable the companion with `pi install npm:@plannotator/pi-extension`, then restart Pi or run `/reload`. The canonical unscoped `implannotator` package and scoped `@yoseph_23/implannotator` 0.1.1 do not bundle a second copy. Scoped version 0.1.0 should be removed because it can register duplicate tools and flags.
|
|
10
|
+
|
|
11
|
+
## Run interrupted
|
|
12
|
+
|
|
13
|
+
Use `/implannotator status` then `/implannotator resume`. Inspect `.implannotator/runs/<run-id>/run.json` and do not repeat completed mutations.
|
|
14
|
+
|
|
15
|
+
## Mutation blocked
|
|
16
|
+
|
|
17
|
+
The plan has not been approved, the path is outside the active project, or the command is unsafe Git automation. Complete the approval step or perform the action manually outside the workflow after reviewing the risk.
|
package/docs/workflow.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Workflow
|
|
2
|
+
|
|
3
|
+
The durable state machine is:
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
inspecting -> planning -> awaiting plan review -> approved -> implementing
|
|
7
|
+
-> QA -> initial review -> repair/QA (up to 3) -> final review -> complete
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Rejection or close offers Revise, Proceed anyway, or Stop. Proceed anyway requires a second warning and creates a durable override decision. Browser-unavailable sessions use text approval; conservative low-risk auto-approval is disabled by default.
|
|
11
|
+
|
|
12
|
+
Review cadence is fixed: one plan tab, one initial code-review tab, no intermediate repair tabs, and one final code-review tab.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { Orchestrator } from "../workflow/orchestrator.js";
|
|
3
|
+
|
|
4
|
+
const MANAGEMENT = new Set(["status", "resume", "review", "verify", "config", "update-check"]);
|
|
5
|
+
|
|
6
|
+
export function registerCommands(pi: ExtensionAPI, getOrchestrator: (cwd: string) => Orchestrator): void {
|
|
7
|
+
pi.registerCommand("implannotator", {
|
|
8
|
+
description: "Approval-gated frontend design and implementation: /implannotator [command] [target]",
|
|
9
|
+
handler: async (args, ctx) => handle(args.trim(), pi, ctx, getOrchestrator),
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function handle(args: string, pi: ExtensionAPI, ctx: ExtensionCommandContext, getOrchestrator: (cwd: string) => Orchestrator): Promise<void> {
|
|
14
|
+
const [first = "", ...rest] = splitArgs(args);
|
|
15
|
+
const workflow = getOrchestrator(ctx.cwd);
|
|
16
|
+
if (first === "status") {
|
|
17
|
+
const run = workflow.active();
|
|
18
|
+
ctx.ui.notify(run ? `Implannotator ${run.id}: ${run.phase}, repairs ${run.repairCount}/3, QA ${run.qaProfile}` : "No Implannotator run exists here.", "info");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (first === "config") {
|
|
22
|
+
ctx.ui.notify("Config precedence: built-in defaults, ~/.pi/agent/implannotator.json, then <project>/.pi/implannotator.json.", "info");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const instruction = !args
|
|
26
|
+
? "Use the implannotator skill. Inspect this frontend project and recommend the 2-3 highest-value commands, but do not implement until I choose and approve a plan."
|
|
27
|
+
: MANAGEMENT.has(first)
|
|
28
|
+
? `Use the implannotator skill and ${first}${rest.length ? ` ${rest.join(" ")}` : ""} the active workflow.`
|
|
29
|
+
: `Use the implannotator skill. Start or continue an approval-gated run for command: ${first}${rest.length ? `; target: ${rest.join(" ")}` : ""}.`;
|
|
30
|
+
pi.sendUserMessage(instruction);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function splitArgs(input: string): string[] {
|
|
34
|
+
const result: string[] = [];
|
|
35
|
+
const pattern = /"([^"]*)"|'([^']*)'|([^\s]+)/g;
|
|
36
|
+
let match: RegExpExecArray | null;
|
|
37
|
+
while ((match = pattern.exec(input))) result.push(match[1] ?? match[2] ?? match[3] ?? "");
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ImplannotatorConfig } from "../domain/types.js";
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_CONFIG: ImplannotatorConfig = {
|
|
4
|
+
schemaVersion: 1,
|
|
5
|
+
approval: {
|
|
6
|
+
required: true,
|
|
7
|
+
allowLowRiskAutoApprove: false,
|
|
8
|
+
proceedRequiresSecondConfirmation: true,
|
|
9
|
+
browserTimeoutMs: 30 * 60 * 1000,
|
|
10
|
+
},
|
|
11
|
+
repairs: { maxAttempts: 3, reopenOnlyForFinalReview: true },
|
|
12
|
+
qa: { defaultProfile: "adaptive", requireEvidence: true },
|
|
13
|
+
audit: { directory: ".implannotator/runs", retentionDays: 90, redactSensitiveValues: true },
|
|
14
|
+
git: { allowAutomaticCommit: false, allowAutomaticStash: false, preserveExistingChanges: true },
|
|
15
|
+
subagents: { enabled: true, readOnly: true, mainAgentSoleWriter: true },
|
|
16
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { isAbsolute, join } from "node:path";
|
|
4
|
+
import { DEFAULT_CONFIG } from "./defaults.js";
|
|
5
|
+
import type { ImplannotatorConfig } from "../domain/types.js";
|
|
6
|
+
|
|
7
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
8
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function merge<T>(base: T, override: unknown): T {
|
|
12
|
+
if (!isRecord(base) || !isRecord(override)) return (override === undefined ? base : override) as T;
|
|
13
|
+
const result: Record<string, unknown> = { ...base };
|
|
14
|
+
for (const [key, value] of Object.entries(override)) {
|
|
15
|
+
result[key] = isRecord(value) && isRecord(result[key]) ? merge(result[key], value) : value;
|
|
16
|
+
}
|
|
17
|
+
return result as T;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function readJson(path: string): unknown {
|
|
21
|
+
if (!existsSync(path)) return undefined;
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
24
|
+
} catch (error) {
|
|
25
|
+
throw new Error(`Invalid Implannotator configuration at ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const CONFIG_KEYS: Record<string, readonly string[]> = {
|
|
30
|
+
root: ["schemaVersion", "approval", "repairs", "qa", "audit", "git", "subagents"],
|
|
31
|
+
approval: ["required", "allowLowRiskAutoApprove", "proceedRequiresSecondConfirmation", "browserTimeoutMs"],
|
|
32
|
+
repairs: ["maxAttempts", "reopenOnlyForFinalReview"],
|
|
33
|
+
qa: ["defaultProfile", "requireEvidence"],
|
|
34
|
+
audit: ["directory", "retentionDays", "redactSensitiveValues"],
|
|
35
|
+
git: ["allowAutomaticCommit", "allowAutomaticStash", "preserveExistingChanges"],
|
|
36
|
+
subagents: ["enabled", "readOnly", "mainAgentSoleWriter"],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function assertKnownConfig(value: unknown, source: string): void {
|
|
40
|
+
if (value === undefined) return;
|
|
41
|
+
if (!isRecord(value)) throw new Error(`Implannotator configuration at ${source} must be an object.`);
|
|
42
|
+
for (const key of Object.keys(value)) {
|
|
43
|
+
if (!CONFIG_KEYS.root!.includes(key)) throw new Error(`Unknown Implannotator setting ${key} in ${source}.`);
|
|
44
|
+
if (key !== "schemaVersion" && value[key] !== undefined) {
|
|
45
|
+
if (!isRecord(value[key])) throw new Error(`Implannotator setting ${key} in ${source} must be an object.`);
|
|
46
|
+
for (const nested of Object.keys(value[key])) {
|
|
47
|
+
if (!CONFIG_KEYS[key]!.includes(nested)) throw new Error(`Unknown Implannotator setting ${key}.${nested} in ${source}.`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function validateConfig(config: ImplannotatorConfig): ImplannotatorConfig {
|
|
54
|
+
if (config.schemaVersion !== 1) throw new Error("Unsupported Implannotator config schemaVersion.");
|
|
55
|
+
if (config.approval.required !== true) throw new Error("approval.required cannot be disabled.");
|
|
56
|
+
if (config.approval.proceedRequiresSecondConfirmation !== true) throw new Error("Proceed confirmation cannot be disabled.");
|
|
57
|
+
if (!Number.isInteger(config.approval.browserTimeoutMs) || config.approval.browserTimeoutMs < 1_000) throw new Error("approval.browserTimeoutMs must be at least 1000.");
|
|
58
|
+
if (!Number.isInteger(config.repairs.maxAttempts) || config.repairs.maxAttempts < 0 || config.repairs.maxAttempts > 3) {
|
|
59
|
+
throw new Error("repairs.maxAttempts must be an integer from 0 through 3.");
|
|
60
|
+
}
|
|
61
|
+
if (config.repairs.reopenOnlyForFinalReview !== true) throw new Error("Intermediate repair review tabs cannot be enabled.");
|
|
62
|
+
if (!['adaptive', 'quick', 'standard', 'full'].includes(config.qa.defaultProfile)) throw new Error("qa.defaultProfile is invalid.");
|
|
63
|
+
if (isAbsolute(config.audit.directory) || config.audit.directory.split(/[\\/]+/).includes("..")) throw new Error("audit.directory must stay inside the project.");
|
|
64
|
+
if (!Number.isInteger(config.audit.retentionDays) || config.audit.retentionDays < 1) throw new Error("audit.retentionDays must be a positive integer.");
|
|
65
|
+
if (!config.audit.redactSensitiveValues) throw new Error("Audit redaction cannot be disabled.");
|
|
66
|
+
if (config.git.allowAutomaticCommit || config.git.allowAutomaticStash || !config.git.preserveExistingChanges) {
|
|
67
|
+
throw new Error("Unsafe Git automation settings are not supported.");
|
|
68
|
+
}
|
|
69
|
+
if (!config.subagents.readOnly || !config.subagents.mainAgentSoleWriter) {
|
|
70
|
+
throw new Error("Subagents must remain read-only and the main agent must be the sole writer.");
|
|
71
|
+
}
|
|
72
|
+
return config;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function loadConfig(cwd: string): ImplannotatorConfig {
|
|
76
|
+
const globalPath = join(homedir(), ".pi", "agent", "implannotator.json");
|
|
77
|
+
const projectPath = join(cwd, ".pi", "implannotator.json");
|
|
78
|
+
const globalConfig = readJson(globalPath);
|
|
79
|
+
const projectConfig = readJson(projectPath);
|
|
80
|
+
assertKnownConfig(globalConfig, globalPath);
|
|
81
|
+
assertKnownConfig(projectConfig, projectPath);
|
|
82
|
+
const config = merge(merge(DEFAULT_CONFIG, globalConfig), projectConfig);
|
|
83
|
+
return validateConfig(config);
|
|
84
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export const PHASES = [
|
|
2
|
+
"inspecting",
|
|
3
|
+
"planning",
|
|
4
|
+
"awaiting-plan-review",
|
|
5
|
+
"awaiting-text-approval",
|
|
6
|
+
"approved",
|
|
7
|
+
"implementing",
|
|
8
|
+
"qa",
|
|
9
|
+
"initial-review",
|
|
10
|
+
"repairing",
|
|
11
|
+
"final-review",
|
|
12
|
+
"complete",
|
|
13
|
+
"stopped",
|
|
14
|
+
"failed",
|
|
15
|
+
] as const;
|
|
16
|
+
|
|
17
|
+
export type RunPhase = (typeof PHASES)[number];
|
|
18
|
+
export type QaProfile = "quick" | "standard" | "full";
|
|
19
|
+
export type RiskLevel = "low" | "medium" | "high";
|
|
20
|
+
export type ReviewStage = "plan" | "initial" | "final";
|
|
21
|
+
export type EvidenceStatus = "passed" | "failed" | "unavailable" | "not-applicable";
|
|
22
|
+
|
|
23
|
+
export interface ImplannotatorConfig {
|
|
24
|
+
schemaVersion: 1;
|
|
25
|
+
approval: {
|
|
26
|
+
required: true;
|
|
27
|
+
allowLowRiskAutoApprove: boolean;
|
|
28
|
+
proceedRequiresSecondConfirmation: true;
|
|
29
|
+
browserTimeoutMs: number;
|
|
30
|
+
};
|
|
31
|
+
repairs: { maxAttempts: number; reopenOnlyForFinalReview: true };
|
|
32
|
+
qa: { defaultProfile: "adaptive" | QaProfile; requireEvidence: boolean };
|
|
33
|
+
audit: { directory: string; retentionDays: number; redactSensitiveValues: boolean };
|
|
34
|
+
git: { allowAutomaticCommit: false; allowAutomaticStash: false; preserveExistingChanges: true };
|
|
35
|
+
subagents: { enabled: boolean; readOnly: true; mainAgentSoleWriter: true };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface DecisionRecord {
|
|
39
|
+
at: string;
|
|
40
|
+
kind: string;
|
|
41
|
+
actor: "user" | "agent" | "system";
|
|
42
|
+
decision: string;
|
|
43
|
+
reason?: string;
|
|
44
|
+
warned?: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface QaCheck {
|
|
48
|
+
id: string;
|
|
49
|
+
label: string;
|
|
50
|
+
status: EvidenceStatus;
|
|
51
|
+
evidence?: string[];
|
|
52
|
+
notes?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface QaReport {
|
|
56
|
+
schemaVersion: 1;
|
|
57
|
+
profile: QaProfile;
|
|
58
|
+
createdAt: string;
|
|
59
|
+
checks: QaCheck[];
|
|
60
|
+
blockingFailures: string[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ReviewRecord {
|
|
64
|
+
stage: ReviewStage;
|
|
65
|
+
at: string;
|
|
66
|
+
approved: boolean;
|
|
67
|
+
feedback?: string;
|
|
68
|
+
annotations?: unknown[];
|
|
69
|
+
fallback?: "text";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface RunRecord {
|
|
73
|
+
schemaVersion: 1;
|
|
74
|
+
id: string;
|
|
75
|
+
cwd: string;
|
|
76
|
+
command: string;
|
|
77
|
+
target?: string;
|
|
78
|
+
risk: RiskLevel;
|
|
79
|
+
phase: RunPhase;
|
|
80
|
+
resumePhase?: Exclude<RunPhase, "complete" | "stopped" | "failed" | "implementing" | "repairing">;
|
|
81
|
+
createdAt: string;
|
|
82
|
+
updatedAt: string;
|
|
83
|
+
plan?: string;
|
|
84
|
+
planReviewId?: string;
|
|
85
|
+
pendingTextReview?: ReviewStage;
|
|
86
|
+
repairCount: number;
|
|
87
|
+
qaProfile: QaProfile;
|
|
88
|
+
changedFiles: string[];
|
|
89
|
+
baselineFiles?: Record<string, string>;
|
|
90
|
+
decisions: DecisionRecord[];
|
|
91
|
+
reviews: ReviewRecord[];
|
|
92
|
+
lastError?: string;
|
|
93
|
+
completionOverride?: boolean;
|
|
94
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { registerCommands } from "./commands/register.js";
|
|
3
|
+
import { loadConfig } from "./config/loader.js";
|
|
4
|
+
import { registerMutationGate } from "./policy/mutation-gate.js";
|
|
5
|
+
import { getPlannotatorAvailability } from "./plannotator/availability.js";
|
|
6
|
+
import { registerControlTool } from "./tools/control-tool.js";
|
|
7
|
+
import { Orchestrator } from "./workflow/orchestrator.js";
|
|
8
|
+
|
|
9
|
+
export default function implannotator(pi: ExtensionAPI): void {
|
|
10
|
+
const workflows = new Map<string, Orchestrator>();
|
|
11
|
+
const getOrchestrator = (cwd: string) => {
|
|
12
|
+
let workflow = workflows.get(cwd);
|
|
13
|
+
if (!workflow) {
|
|
14
|
+
workflow = new Orchestrator(pi, cwd, loadConfig(cwd));
|
|
15
|
+
workflows.set(cwd, workflow);
|
|
16
|
+
}
|
|
17
|
+
return workflow;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
registerControlTool(pi, getOrchestrator);
|
|
21
|
+
registerCommands(pi, getOrchestrator);
|
|
22
|
+
registerMutationGate(pi, (cwd) => getOrchestrator(cwd).active());
|
|
23
|
+
|
|
24
|
+
pi.on("session_start", (_event, ctx) => {
|
|
25
|
+
const plannotator = getPlannotatorAvailability(pi);
|
|
26
|
+
if (plannotator.available) {
|
|
27
|
+
ctx.ui.setStatus("implannotator-plannotator", undefined);
|
|
28
|
+
} else {
|
|
29
|
+
ctx.ui.setStatus("implannotator-plannotator", "Implannotator: text approval fallback");
|
|
30
|
+
ctx.ui.notify(
|
|
31
|
+
"Implannotator: Plannotator companion is not active. Install npm:@plannotator/pi-extension for browser reviews; text approval fallback remains available.",
|
|
32
|
+
"warning",
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const run = getOrchestrator(ctx.cwd).active();
|
|
37
|
+
if (run && !["complete", "stopped", "failed"].includes(run.phase)) {
|
|
38
|
+
ctx.ui.setStatus("implannotator", `Implannotator: ${run.phase}`);
|
|
39
|
+
ctx.ui.notify(`Resumable Implannotator run ${run.id} is in ${run.phase}. Use /implannotator resume.`, "info");
|
|
40
|
+
} else {
|
|
41
|
+
ctx.ui.setStatus("implannotator", undefined);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
export interface PlannotatorAvailability {
|
|
4
|
+
available: boolean;
|
|
5
|
+
tool: boolean;
|
|
6
|
+
command: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function getPlannotatorAvailability(pi: ExtensionAPI): PlannotatorAvailability {
|
|
10
|
+
const tool = pi.getAllTools().some((candidate) => candidate.name === "plannotator_submit_plan");
|
|
11
|
+
const command = pi.getCommands().some((candidate) => candidate.name === "plannotator");
|
|
12
|
+
return { available: tool || command, tool, command };
|
|
13
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import type { CodeReviewResult, PlanReviewStarted, PlannotatorAction, PlannotatorResponse, ReviewStatus } from "./contracts.js";
|
|
4
|
+
|
|
5
|
+
const REQUEST_CHANNEL = "plannotator:request";
|
|
6
|
+
|
|
7
|
+
function delay(ms: number, signal?: AbortSignal): Promise<void> {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const timer = setTimeout(resolve, ms);
|
|
10
|
+
signal?.addEventListener("abort", () => { clearTimeout(timer); reject(signal.reason ?? new Error("Aborted")); }, { once: true });
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class PlannotatorClient {
|
|
15
|
+
constructor(private readonly pi: ExtensionAPI, private readonly responseTimeoutMs = 5_000) {}
|
|
16
|
+
|
|
17
|
+
request<T>(action: PlannotatorAction, payload: Record<string, unknown>, signal?: AbortSignal): Promise<PlannotatorResponse<T>> {
|
|
18
|
+
return new Promise((resolve) => {
|
|
19
|
+
let settled = false;
|
|
20
|
+
const finish = (response: PlannotatorResponse<T>) => {
|
|
21
|
+
if (settled) return;
|
|
22
|
+
settled = true;
|
|
23
|
+
clearTimeout(timer);
|
|
24
|
+
resolve(response);
|
|
25
|
+
};
|
|
26
|
+
const timer = setTimeout(() => finish({ status: "unavailable", error: `Plannotator did not handle ${action} within ${this.responseTimeoutMs}ms.` }), this.responseTimeoutMs);
|
|
27
|
+
signal?.addEventListener("abort", () => finish({ status: "error", error: "Plannotator request aborted." }), { once: true });
|
|
28
|
+
this.pi.events.emit(REQUEST_CHANNEL, { requestId: randomUUID(), action, payload, respond: finish });
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async startPlanReview(planContent: string, signal?: AbortSignal): Promise<PlannotatorResponse<PlanReviewStarted>> {
|
|
33
|
+
return this.request("plan-review", { planContent, origin: "implannotator" }, signal);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async reviewStatus(reviewId: string, signal?: AbortSignal): Promise<PlannotatorResponse<ReviewStatus>> {
|
|
37
|
+
return this.request("review-status", { reviewId }, signal);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async waitForPlanDecision(reviewId: string, timeoutMs: number, signal?: AbortSignal): Promise<ReviewStatus> {
|
|
41
|
+
const deadline = Date.now() + timeoutMs;
|
|
42
|
+
while (Date.now() < deadline) {
|
|
43
|
+
const response = await this.reviewStatus(reviewId, signal);
|
|
44
|
+
if (response.status !== "handled") throw new Error(response.error ?? "Plannotator review status unavailable.");
|
|
45
|
+
if (response.result.status !== "pending") return response.result;
|
|
46
|
+
await delay(500, signal);
|
|
47
|
+
}
|
|
48
|
+
throw new Error("Timed out waiting for plan approval.");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async codeReview(cwd: string, signal?: AbortSignal): Promise<PlannotatorResponse<CodeReviewResult>> {
|
|
52
|
+
return this.request("code-review", { cwd, diffType: "uncommitted", useLocal: true }, signal);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type PlannotatorAction = "plan-mode" | "plan-review" | "review-status" | "code-review" | "annotate" | "annotate-last" | "archive";
|
|
2
|
+
|
|
3
|
+
export type PlannotatorResponse<T> =
|
|
4
|
+
| { status: "handled"; result: T }
|
|
5
|
+
| { status: "unavailable"; error?: string }
|
|
6
|
+
| { status: "error"; error: string };
|
|
7
|
+
|
|
8
|
+
export interface PlanReviewStarted { status: "pending"; reviewId: string }
|
|
9
|
+
export type ReviewStatus =
|
|
10
|
+
| { status: "pending" }
|
|
11
|
+
| { status: "missing" }
|
|
12
|
+
| { status: "completed"; reviewId: string; approved: boolean; feedback?: string; savedPath?: string };
|
|
13
|
+
|
|
14
|
+
export interface CodeReviewResult {
|
|
15
|
+
approved: boolean;
|
|
16
|
+
feedback?: string;
|
|
17
|
+
annotations?: unknown[];
|
|
18
|
+
agentSwitch?: string;
|
|
19
|
+
exit?: boolean;
|
|
20
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { diffSnapshots, snapshotFiles } from "./non-git-diff.js";
|
|
5
|
+
|
|
6
|
+
export function hasGit(cwd: string): boolean {
|
|
7
|
+
return existsSync(join(cwd, ".git"));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function baselineFiles(cwd: string): Record<string, string> | undefined {
|
|
11
|
+
if (hasGit(cwd)) return undefined;
|
|
12
|
+
try { return snapshotFiles(cwd); } catch { return {}; }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function changedFiles(cwd: string, baseline?: Record<string, string>): string[] {
|
|
16
|
+
if (!hasGit(cwd)) return baseline ? diffSnapshots(baseline, snapshotFiles(cwd)) : [];
|
|
17
|
+
try {
|
|
18
|
+
const output = execFileSync("git", ["status", "--porcelain=v1", "-z"], { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
19
|
+
return output.split("\0").filter(Boolean).map((entry) => entry.slice(3)).filter(Boolean).sort();
|
|
20
|
+
} catch {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
}
|