@xzzpig/pi-openspec-x 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +3 -0
- package/dist/public.d.ts +48 -0
- package/package.json +13 -7
- package/src/child-report-reader.ts +16 -9
- package/src/plan-gate.ts +47 -30
- package/src/report-tools.ts +68 -66
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.2 — 2026-11-05
|
|
4
|
+
|
|
5
|
+
- Require Pi 1.5.0 or newer and build tool parameter schemas with
|
|
6
|
+
`@earendil-works/pi-schema` instead of TypeBox. Preserve existing required
|
|
7
|
+
fields, bounds, and open/closed-object validation semantics.
|
|
8
|
+
|
|
9
|
+
## 0.2.1
|
|
10
|
+
|
|
11
|
+
- Support Pi 1.2+ custom-message wakeups in child report handlers. A wakeup
|
|
12
|
+
retains the current user-dispatch attempt without allowing an unverified
|
|
13
|
+
message to authorize a report; a new user dispatch creates a fresh attempt.
|
|
14
|
+
- Include the extension's generated public type declaration in the package.
|
|
15
|
+
|
|
3
16
|
## 0.2.0
|
|
4
17
|
|
|
5
18
|
Child report tools are now scoped to the subagents that own them, and the
|
package/README.md
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
把 [OpenSpec](https://github.com/Fission-AI/OpenSpec) 的规范驱动流程接进 pi coding agent:从**当前安装的 openspec CLI** 现场提取官方 skill,并提供 `/opsx:plan`(深度计划 + 缺口分析 / 计划审查门)与 `/opsx:implement`(代理实现 / 主会话直实现 + 最终整体审查门)两条增强流程。
|
|
4
4
|
|
|
5
|
+
Requires **Pi >=1.5.0 <2**. Tool schemas use `@earendil-works/pi-schema`,
|
|
6
|
+
which is declared as a Pi peer dependency.
|
|
7
|
+
|
|
5
8
|
```bash
|
|
6
9
|
pi install npm:@xzzpig/pi-openspec-x
|
|
7
10
|
```
|
package/dist/public.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { ExtensionAPI } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* pi-openspec-x extension entry point.
|
|
5
|
+
*
|
|
6
|
+
* pi-sandbox is probed lazily through a dynamic import (dependencies.ts): on
|
|
7
|
+
* success the three opsx sandbox profiles (D4: opsx-planner / opsx-agent /
|
|
8
|
+
* opsx-reviewer) are registered into pi-sandbox's runtime registry — in-memory
|
|
9
|
+
* for the session lifetime, never written to a user's sandbox.json, a
|
|
10
|
+
* user-defined same-name profile always keeping priority. On failure the
|
|
11
|
+
* reason is recorded as dependency state and the extension continues: the
|
|
12
|
+
* official-track skills and every other capability keep working, and a later
|
|
13
|
+
* request for a restricted mode is refused with a typed
|
|
14
|
+
* OpsxDependencyMissingError (fail-closed) instead of the extension (or the
|
|
15
|
+
* official track) dying at load time.
|
|
16
|
+
*
|
|
17
|
+
* Registers the resources_discover handler, which:
|
|
18
|
+
* - generates (and caches, keyed by CLI version) the official-track
|
|
19
|
+
* openspec-* skills under `<agentDir>/cache/pi-openspec-x/skills/` and
|
|
20
|
+
* exposes the directory to pi via `skillPaths`;
|
|
21
|
+
* - warns once per project when static `<cwd>/.pi/skills/openspec-*`
|
|
22
|
+
* directories collide with the generated skills.
|
|
23
|
+
*
|
|
24
|
+
* Registers the session_start handler that clears a stale `active_agent`
|
|
25
|
+
* identity left in the session file by an earlier session (pi-agent-role's
|
|
26
|
+
* semantics; see mode.ts).
|
|
27
|
+
*
|
|
28
|
+
* Registers the three child reporting tools (report_gap_analysis,
|
|
29
|
+
* report_plan_review, report_work) through the role-specific child-only
|
|
30
|
+
* extensions declared in agents.ts (design D1): each tool is registered into
|
|
31
|
+
* its own subagent's process and never into the main session, so the parent's
|
|
32
|
+
* tool declaration and prompt rules stay free of them.
|
|
33
|
+
*
|
|
34
|
+
* pi-subagents is probed the same lazy way: on success the four opsx
|
|
35
|
+
* subagents (opsx-gap-analysis / opsx-plan-review / opsx-worker / opsx-reviewer) are
|
|
36
|
+
* registered into pi-subagents' runtime registry via the owner's event bus
|
|
37
|
+
* (D3). A missing pi-subagents costs only the enhanced flow's subagents —
|
|
38
|
+
* the official track and direct main-session implementation are unaffected.
|
|
39
|
+
* Because the owner listener only exists once pi-subagents has loaded (load
|
|
40
|
+
* order between extensions is not guaranteed), a failed init-time attempt is
|
|
41
|
+
* retried on session_start.
|
|
42
|
+
*
|
|
43
|
+
* Missing openspec CLI: no skills are registered and no error escapes — a
|
|
44
|
+
* one-time notice is sent instead.
|
|
45
|
+
*/
|
|
46
|
+
declare function piOpenspecX(pi: ExtensionAPI): Promise<void>;
|
|
47
|
+
|
|
48
|
+
export { piOpenspecX as default };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xzzpig/pi-openspec-x",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "OpenSpec workflow for the pi coding agent: spec-driven planning and implementation via dedicated subagents, sandboxed /opsx:plan and /opsx:implement commands.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
"index.ts",
|
|
27
27
|
"src",
|
|
28
|
+
"dist",
|
|
28
29
|
"README.md",
|
|
29
30
|
"CHANGELOG.md",
|
|
30
31
|
"LICENSE",
|
|
@@ -36,20 +37,25 @@
|
|
|
36
37
|
]
|
|
37
38
|
},
|
|
38
39
|
"peerDependencies": {
|
|
39
|
-
"@earendil-works/pi-ai": ">=1.
|
|
40
|
-
"@earendil-works/pi-coding-agent": ">=1.
|
|
40
|
+
"@earendil-works/pi-ai": ">=1.5.0 <2",
|
|
41
|
+
"@earendil-works/pi-coding-agent": ">=1.5.0 <2",
|
|
41
42
|
"@xzzpig/pi-goal-x": "*",
|
|
42
43
|
"@xzzpig/pi-sandbox": "*",
|
|
43
|
-
"@xzzpig/pi-subagents": "*"
|
|
44
|
+
"@xzzpig/pi-subagents": "*",
|
|
45
|
+
"@earendil-works/pi-schema": ">=1.5.0 <2"
|
|
44
46
|
},
|
|
45
47
|
"devDependencies": {
|
|
46
|
-
"@earendil-works/pi-coding-agent": "1.
|
|
48
|
+
"@earendil-works/pi-coding-agent": "1.5.0",
|
|
47
49
|
"@types/node": "24.12.4",
|
|
50
|
+
"rollup": "^4.62.2",
|
|
51
|
+
"rollup-plugin-dts": "^6.4.1",
|
|
48
52
|
"typescript": "5.9.3",
|
|
49
|
-
"vitest": "4.1.8"
|
|
53
|
+
"vitest": "4.1.8",
|
|
54
|
+
"@earendil-works/pi-schema": "1.5.0"
|
|
50
55
|
},
|
|
51
56
|
"scripts": {
|
|
52
57
|
"test": "vitest run",
|
|
53
|
-
"typecheck": "tsc --noEmit"
|
|
58
|
+
"typecheck": "tsc --noEmit",
|
|
59
|
+
"build:types": "rollup -c rollup.dts.config.mjs"
|
|
54
60
|
}
|
|
55
61
|
}
|
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
* ingestion; it never adds a model-fillable parameter, and the parent
|
|
13
13
|
* re-validates `report` against the original tool schema.
|
|
14
14
|
*
|
|
15
|
-
* The attempt is the anti-forgery anchor:
|
|
16
|
-
* random `attemptId` plus the SHA-256 of
|
|
17
|
-
* `pi-openspec-x/child-report-attempt:v1` session entry.
|
|
18
|
-
* attributable to the
|
|
19
|
-
* reuses the same session file cannot smuggle an earlier
|
|
20
|
-
* current dispatch.
|
|
15
|
+
* The attempt is the anti-forgery anchor: for each user dispatch,
|
|
16
|
+
* `before_agent_start` records a fresh random `attemptId` plus the SHA-256 of
|
|
17
|
+
* the actual prompt in a `pi-openspec-x/child-report-attempt:v1` session entry.
|
|
18
|
+
* A report is attributable only to the dispatch whose attempt it names, so a
|
|
19
|
+
* resumed child that reuses the same session file cannot smuggle an earlier
|
|
20
|
+
* run's report into the current dispatch.
|
|
21
21
|
*
|
|
22
22
|
* Deliberately NOT here: any reading of pi-subagents' private `AsyncStatus`
|
|
23
23
|
* or lifecycle constants (design D3). This module declares the minimal v3
|
|
@@ -164,9 +164,9 @@ export function beginChildReportAttempt(
|
|
|
164
164
|
/** Human-readable refusal text for a report that cannot be attributed. */
|
|
165
165
|
export function attemptUnavailableMessage(agent: string): string {
|
|
166
166
|
return (
|
|
167
|
-
`Report rejected: no child report attempt is recorded for ${agent} in this
|
|
167
|
+
`Report rejected: no child report attempt is recorded for ${agent} in this dispatch. ` +
|
|
168
168
|
`The parent cannot attribute this report to a verified run, so it would be ignored; ` +
|
|
169
|
-
`the attempt entry is written by this extension at the start of
|
|
169
|
+
`the attempt entry is written by this extension at the start of each user dispatch.`
|
|
170
170
|
);
|
|
171
171
|
}
|
|
172
172
|
|
|
@@ -175,7 +175,7 @@ export function attemptUnavailableMessage(agent: string): string {
|
|
|
175
175
|
*
|
|
176
176
|
* The wrapped tool keeps the original name, schema, confirmation text and
|
|
177
177
|
* `sequential` execution mode; only `details` changes into the envelope. When
|
|
178
|
-
* the current
|
|
178
|
+
* the current dispatch has no persisted attempt, the call fails explicitly instead
|
|
179
179
|
* of returning a report the parent would have to discard.
|
|
180
180
|
*/
|
|
181
181
|
export function registerChildReportTool(
|
|
@@ -185,6 +185,13 @@ export function registerChildReportTool(
|
|
|
185
185
|
let current: ChildReportAttemptOutcome | undefined;
|
|
186
186
|
|
|
187
187
|
pi.on("before_agent_start", (event: BeforeAgentStartEvent, ctx) => {
|
|
188
|
+
// Pi 1.2+ also fires this hook when a custom message wakes the same child.
|
|
189
|
+
// It is not a new user dispatch: keep its existing attempt, or continue to
|
|
190
|
+
// reject reports if no dispatch has been recorded. Pi 1.1 never fired this
|
|
191
|
+
// hook for such a wake and has no messageType field.
|
|
192
|
+
if ("messageType" in event && event.messageType === "custom") return;
|
|
193
|
+
current = undefined;
|
|
194
|
+
if (!("prompt" in event) || typeof event.prompt !== "string") return;
|
|
188
195
|
current = beginChildReportAttempt(pi, ctx, {
|
|
189
196
|
agent: options.agent,
|
|
190
197
|
prompt: event.prompt,
|
package/src/plan-gate.ts
CHANGED
|
@@ -17,7 +17,16 @@
|
|
|
17
17
|
* reads the recorded approval from disk, so approval survives a restart and a
|
|
18
18
|
* fresh session cannot bypass the gate.
|
|
19
19
|
*/
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
arr,
|
|
22
|
+
lit,
|
|
23
|
+
num,
|
|
24
|
+
obj,
|
|
25
|
+
opt,
|
|
26
|
+
str,
|
|
27
|
+
union,
|
|
28
|
+
type Static,
|
|
29
|
+
} from "@earendil-works/pi-schema";
|
|
21
30
|
import {
|
|
22
31
|
defineTool,
|
|
23
32
|
type ExtensionAPI,
|
|
@@ -43,61 +52,57 @@ import {
|
|
|
43
52
|
export const OPSX_PLAN_GAP_ANALYSIS_TOOL_NAME = "opsx_plan_gap_analysis";
|
|
44
53
|
export const OPSX_PLAN_VERDICT_TOOL_NAME = "opsx_plan_verdict";
|
|
45
54
|
|
|
46
|
-
const usageSchema =
|
|
47
|
-
|
|
55
|
+
const usageSchema = opt(
|
|
56
|
+
obj(
|
|
48
57
|
{
|
|
49
|
-
input:
|
|
50
|
-
output:
|
|
51
|
-
cacheRead:
|
|
52
|
-
cacheWrite:
|
|
53
|
-
turns:
|
|
58
|
+
input: num(),
|
|
59
|
+
output: num(),
|
|
60
|
+
cacheRead: num(),
|
|
61
|
+
cacheWrite: num(),
|
|
62
|
+
turns: num(),
|
|
54
63
|
},
|
|
55
|
-
{
|
|
64
|
+
{ open: false },
|
|
56
65
|
),
|
|
57
66
|
);
|
|
58
67
|
|
|
59
|
-
const gapAnalysisParams =
|
|
68
|
+
const gapAnalysisParams = obj(
|
|
60
69
|
{
|
|
61
|
-
findings:
|
|
62
|
-
|
|
70
|
+
findings: arr(
|
|
71
|
+
str({
|
|
63
72
|
description: "One concrete gap, specific enough to act on.",
|
|
64
73
|
}),
|
|
65
74
|
{ description: "Gap-analysis findings; may be empty." },
|
|
66
75
|
),
|
|
67
|
-
summary:
|
|
68
|
-
changeId:
|
|
69
|
-
|
|
76
|
+
summary: opt(str()),
|
|
77
|
+
changeId: opt(
|
|
78
|
+
str({
|
|
70
79
|
description:
|
|
71
80
|
"OpenSpec change id; defaults to this session's active plan flow.",
|
|
72
81
|
}),
|
|
73
82
|
),
|
|
74
83
|
},
|
|
75
|
-
{
|
|
84
|
+
{ open: false },
|
|
76
85
|
);
|
|
77
86
|
|
|
78
|
-
const planReviewParams =
|
|
87
|
+
const planReviewParams = obj(
|
|
79
88
|
{
|
|
80
|
-
verdict:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
Type.Literal("REJECT"),
|
|
84
|
-
]),
|
|
85
|
-
blockers: Type.Optional(
|
|
86
|
-
Type.Array(Type.String(), {
|
|
89
|
+
verdict: union([lit("OKAY"), lit("ITERATE"), lit("REJECT")]),
|
|
90
|
+
blockers: opt(
|
|
91
|
+
arr(str(), {
|
|
87
92
|
description:
|
|
88
93
|
"Blocking issues; at most three, each new relative to earlier rounds.",
|
|
89
94
|
}),
|
|
90
95
|
),
|
|
91
|
-
summary:
|
|
96
|
+
summary: opt(str()),
|
|
92
97
|
usage: usageSchema,
|
|
93
|
-
changeId:
|
|
94
|
-
|
|
98
|
+
changeId: opt(
|
|
99
|
+
str({
|
|
95
100
|
description:
|
|
96
101
|
"OpenSpec change id; defaults to this session's active plan flow.",
|
|
97
102
|
}),
|
|
98
103
|
),
|
|
99
104
|
},
|
|
100
|
-
{
|
|
105
|
+
{ open: false },
|
|
101
106
|
);
|
|
102
107
|
|
|
103
108
|
/**
|
|
@@ -149,7 +154,13 @@ export function registerPlanGateTools(pi: ExtensionAPI): void {
|
|
|
149
154
|
],
|
|
150
155
|
parameters: gapAnalysisParams,
|
|
151
156
|
executionMode: "sequential",
|
|
152
|
-
async execute(
|
|
157
|
+
async execute(
|
|
158
|
+
_toolCallId,
|
|
159
|
+
params: Static<typeof gapAnalysisParams>,
|
|
160
|
+
_signal,
|
|
161
|
+
_onUpdate,
|
|
162
|
+
ctx,
|
|
163
|
+
) {
|
|
153
164
|
const resolved = requireChangeId(ctx, params.changeId);
|
|
154
165
|
if ("error" in resolved) {
|
|
155
166
|
return toolText(resolved.error);
|
|
@@ -181,7 +192,13 @@ export function registerPlanGateTools(pi: ExtensionAPI): void {
|
|
|
181
192
|
],
|
|
182
193
|
parameters: planReviewParams,
|
|
183
194
|
executionMode: "sequential",
|
|
184
|
-
async execute(
|
|
195
|
+
async execute(
|
|
196
|
+
_toolCallId,
|
|
197
|
+
params: Static<typeof planReviewParams>,
|
|
198
|
+
_signal,
|
|
199
|
+
_onUpdate,
|
|
200
|
+
ctx,
|
|
201
|
+
) {
|
|
185
202
|
const resolved = requireChangeId(ctx, params.changeId);
|
|
186
203
|
if ("error" in resolved) {
|
|
187
204
|
return toolText(resolved.error);
|
package/src/report-tools.ts
CHANGED
|
@@ -22,7 +22,17 @@
|
|
|
22
22
|
* call returns. There is no progress bar here — a tool may be called any
|
|
23
23
|
* number of times; projection of the calls is a later concern.
|
|
24
24
|
*/
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
arr,
|
|
27
|
+
bool,
|
|
28
|
+
lit,
|
|
29
|
+
num,
|
|
30
|
+
obj,
|
|
31
|
+
opt,
|
|
32
|
+
str,
|
|
33
|
+
union,
|
|
34
|
+
type Static,
|
|
35
|
+
} from "@earendil-works/pi-schema";
|
|
26
36
|
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
27
37
|
|
|
28
38
|
export const REPORT_GAP_ANALYSIS_TOOL_NAME = "report_gap_analysis";
|
|
@@ -33,20 +43,20 @@ export const REPORT_WORK_TOOL_NAME = "report_work";
|
|
|
33
43
|
* One located finding. `location` is free-form ("src/cli.ts:42",
|
|
34
44
|
* "tasks.md#Step 3") because artifact sections do not always map to lines.
|
|
35
45
|
*/
|
|
36
|
-
const findingItem =
|
|
46
|
+
const findingItem = obj(
|
|
37
47
|
{
|
|
38
|
-
description:
|
|
48
|
+
description: str({
|
|
39
49
|
description:
|
|
40
50
|
"The finding itself, specific enough to act on without re-deriving it.",
|
|
41
51
|
}),
|
|
42
|
-
location:
|
|
43
|
-
|
|
52
|
+
location: opt(
|
|
53
|
+
str({
|
|
44
54
|
description:
|
|
45
55
|
"Where the finding applies: file path, and line or section when known.",
|
|
46
56
|
}),
|
|
47
57
|
),
|
|
48
58
|
},
|
|
49
|
-
{
|
|
59
|
+
{ open: false },
|
|
50
60
|
);
|
|
51
61
|
|
|
52
62
|
/**
|
|
@@ -54,158 +64,150 @@ const findingItem = Type.Object(
|
|
|
54
64
|
* 百分比,或等效字段). Optional: a report that carries no progress simply leaves
|
|
55
65
|
* the parent's board unchanged.
|
|
56
66
|
*/
|
|
57
|
-
const progressField =
|
|
58
|
-
|
|
67
|
+
const progressField = opt(
|
|
68
|
+
obj(
|
|
59
69
|
{
|
|
60
|
-
phase:
|
|
70
|
+
phase: str({
|
|
61
71
|
description:
|
|
62
72
|
"The stage this update belongs to, e.g. reading, analyzing, editing, verifying.",
|
|
63
73
|
}),
|
|
64
|
-
percentage:
|
|
74
|
+
percentage: num({
|
|
65
75
|
description: "How far through this stage the agent is, 0 to 100.",
|
|
66
76
|
minimum: 0,
|
|
67
77
|
maximum: 100,
|
|
68
78
|
}),
|
|
69
|
-
label:
|
|
70
|
-
|
|
79
|
+
label: opt(
|
|
80
|
+
str({
|
|
71
81
|
description:
|
|
72
82
|
"Short human-readable detail for the board; defaults to the phase name.",
|
|
73
83
|
}),
|
|
74
84
|
),
|
|
75
85
|
},
|
|
76
|
-
{
|
|
86
|
+
{ open: false },
|
|
77
87
|
),
|
|
78
88
|
);
|
|
79
89
|
|
|
80
|
-
const gapAnalysisParams =
|
|
90
|
+
const gapAnalysisParams = obj(
|
|
81
91
|
{
|
|
82
92
|
progress: progressField,
|
|
83
|
-
intentType:
|
|
93
|
+
intentType: str({
|
|
84
94
|
description:
|
|
85
95
|
"Classified user intent, e.g. refactor, build-from-scratch, mid-sized, collaborative, architecture, or research.",
|
|
86
96
|
}),
|
|
87
|
-
confidence:
|
|
97
|
+
confidence: num({
|
|
88
98
|
description: "Confidence in the intent classification, from 0 to 1.",
|
|
89
99
|
minimum: 0,
|
|
90
100
|
maximum: 1,
|
|
91
101
|
}),
|
|
92
|
-
contradictions:
|
|
102
|
+
contradictions: arr(findingItem, {
|
|
93
103
|
description: "Requirements or statements that conflict with each other.",
|
|
94
104
|
}),
|
|
95
|
-
missingConstraints:
|
|
105
|
+
missingConstraints: arr(findingItem, {
|
|
96
106
|
description:
|
|
97
107
|
"Constraints the request implies but never states (platform, compatibility, performance, security, ...).",
|
|
98
108
|
}),
|
|
99
|
-
scopeRisks:
|
|
109
|
+
scopeRisks: arr(findingItem, {
|
|
100
110
|
description:
|
|
101
111
|
"Scope-inflation or gold-plating risks, including premature abstraction and over-validation.",
|
|
102
112
|
}),
|
|
103
|
-
unvalidatedAssumptions:
|
|
113
|
+
unvalidatedAssumptions: arr(findingItem, {
|
|
104
114
|
description:
|
|
105
115
|
"Assumptions the plan relies on without evidence (environment, behavior, availability).",
|
|
106
116
|
}),
|
|
107
|
-
missingAcceptanceCriteria:
|
|
117
|
+
missingAcceptanceCriteria: arr(findingItem, {
|
|
108
118
|
description:
|
|
109
119
|
"Outcomes that lack an agent-executable acceptance criterion (no 'user manually tests' criteria).",
|
|
110
120
|
}),
|
|
111
|
-
summary:
|
|
121
|
+
summary: str({
|
|
112
122
|
description: "One-paragraph synthesis of the analysis.",
|
|
113
123
|
}),
|
|
114
124
|
},
|
|
115
|
-
{
|
|
125
|
+
{ open: false },
|
|
116
126
|
);
|
|
117
127
|
|
|
118
|
-
const planReviewParams =
|
|
128
|
+
const planReviewParams = obj(
|
|
119
129
|
{
|
|
120
130
|
progress: progressField,
|
|
121
|
-
verdict:
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
),
|
|
128
|
-
issues: Type.Array(
|
|
129
|
-
Type.Object(
|
|
131
|
+
verdict: union([lit("OKAY"), lit("ITERATE"), lit("REJECT")], {
|
|
132
|
+
description:
|
|
133
|
+
"OKAY: executable as written. ITERATE: workable after targeted, non-blocking revisions. REJECT: not executable as written.",
|
|
134
|
+
}),
|
|
135
|
+
issues: arr(
|
|
136
|
+
obj(
|
|
130
137
|
{
|
|
131
|
-
severity:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
],
|
|
137
|
-
{ description: "How badly the issue threatens execution." },
|
|
138
|
-
),
|
|
139
|
-
file: Type.Optional(
|
|
140
|
-
Type.String({
|
|
138
|
+
severity: union([lit("blocker"), lit("major"), lit("minor")], {
|
|
139
|
+
description: "How badly the issue threatens execution.",
|
|
140
|
+
}),
|
|
141
|
+
file: opt(
|
|
142
|
+
str({
|
|
141
143
|
description: "Plan file the issue applies to, when located.",
|
|
142
144
|
}),
|
|
143
145
|
),
|
|
144
|
-
line:
|
|
145
|
-
|
|
146
|
+
line: opt(
|
|
147
|
+
num({
|
|
146
148
|
description: "Line number in that file, when known.",
|
|
147
149
|
}),
|
|
148
150
|
),
|
|
149
|
-
description:
|
|
151
|
+
description: str({
|
|
150
152
|
description:
|
|
151
153
|
"The issue, concrete and actionable (what to change, not a taste judgment).",
|
|
152
154
|
}),
|
|
153
|
-
blocking:
|
|
155
|
+
blocking: bool({
|
|
154
156
|
description:
|
|
155
157
|
"True only for REJECT blockers; ITERATE/OKAY issues are never blocking.",
|
|
156
158
|
}),
|
|
157
159
|
},
|
|
158
|
-
{
|
|
160
|
+
{ open: false },
|
|
159
161
|
),
|
|
160
162
|
{ description: "Found issues; may be empty for a clean OKAY." },
|
|
161
163
|
),
|
|
162
|
-
summary:
|
|
164
|
+
summary: str({
|
|
163
165
|
description:
|
|
164
166
|
"One-paragraph verdict rationale; for REJECT it must reference which earlier blockers (if any) are now resolved.",
|
|
165
167
|
}),
|
|
166
168
|
},
|
|
167
|
-
{
|
|
169
|
+
{ open: false },
|
|
168
170
|
);
|
|
169
171
|
|
|
170
|
-
const workReportParams =
|
|
172
|
+
const workReportParams = obj(
|
|
171
173
|
{
|
|
172
174
|
progress: progressField,
|
|
173
|
-
taskId:
|
|
175
|
+
taskId: str({
|
|
174
176
|
description: "The dispatched task identifier (e.g. the tasks.md item).",
|
|
175
177
|
}),
|
|
176
|
-
changedFiles:
|
|
178
|
+
changedFiles: arr(str(), {
|
|
177
179
|
description:
|
|
178
180
|
"Every file this task created or modified, with paths relative to the project root.",
|
|
179
181
|
}),
|
|
180
|
-
claims:
|
|
182
|
+
claims: str({
|
|
181
183
|
description:
|
|
182
184
|
"What was done and what the result is. Only claims backed by evidence below.",
|
|
183
185
|
}),
|
|
184
|
-
selfVerification:
|
|
186
|
+
selfVerification: obj(
|
|
185
187
|
{
|
|
186
|
-
command:
|
|
187
|
-
|
|
188
|
+
command: opt(
|
|
189
|
+
str({
|
|
188
190
|
description:
|
|
189
191
|
"The verification command actually run (e.g. build/test).",
|
|
190
192
|
}),
|
|
191
193
|
),
|
|
192
|
-
result:
|
|
193
|
-
|
|
194
|
+
result: opt(
|
|
195
|
+
str({
|
|
194
196
|
description:
|
|
195
197
|
"The real command output summary (exit status, failing/passing counts). Never invented.",
|
|
196
198
|
}),
|
|
197
199
|
),
|
|
198
200
|
},
|
|
199
|
-
{
|
|
201
|
+
{ open: false },
|
|
200
202
|
),
|
|
201
|
-
verificationPassed:
|
|
202
|
-
|
|
203
|
+
verificationPassed: opt(
|
|
204
|
+
bool({
|
|
203
205
|
description:
|
|
204
206
|
"Structured verdict: whether the verification actually passed. When provided the gate trusts it and does not parse the result text; when absent the result text is scanned heuristically.",
|
|
205
207
|
}),
|
|
206
208
|
),
|
|
207
209
|
},
|
|
208
|
-
{
|
|
210
|
+
{ open: false },
|
|
209
211
|
);
|
|
210
212
|
|
|
211
213
|
/** Count of non-empty finding lists, for the one-line confirmation text. */
|
|
@@ -238,7 +240,7 @@ export const reportGapAnalysisTool = defineTool({
|
|
|
238
240
|
],
|
|
239
241
|
parameters: gapAnalysisParams,
|
|
240
242
|
executionMode: "sequential",
|
|
241
|
-
async execute(_toolCallId, params) {
|
|
243
|
+
async execute(_toolCallId, params: Static<typeof gapAnalysisParams>) {
|
|
242
244
|
return {
|
|
243
245
|
content: [
|
|
244
246
|
{
|
|
@@ -264,7 +266,7 @@ export const reportPlanReviewTool = defineTool({
|
|
|
264
266
|
],
|
|
265
267
|
parameters: planReviewParams,
|
|
266
268
|
executionMode: "sequential",
|
|
267
|
-
async execute(_toolCallId, params) {
|
|
269
|
+
async execute(_toolCallId, params: Static<typeof planReviewParams>) {
|
|
268
270
|
const blocking = params.issues.filter((issue) => issue.blocking).length;
|
|
269
271
|
return {
|
|
270
272
|
content: [
|
|
@@ -291,7 +293,7 @@ export const reportWorkTool = defineTool({
|
|
|
291
293
|
],
|
|
292
294
|
parameters: workReportParams,
|
|
293
295
|
executionMode: "sequential",
|
|
294
|
-
async execute(_toolCallId, params) {
|
|
296
|
+
async execute(_toolCallId, params: Static<typeof workReportParams>) {
|
|
295
297
|
return {
|
|
296
298
|
content: [
|
|
297
299
|
{
|