@spences10/pi-harness 0.1.1 → 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/README.md +116 -175
- package/dist/assessment.d.ts +4 -12
- package/dist/assessment.js +19 -192
- package/dist/assessment.js.map +1 -1
- package/dist/enforcement/policy.d.ts +2 -0
- package/dist/enforcement/policy.js +47 -73
- package/dist/enforcement/policy.js.map +1 -1
- package/dist/extension.d.ts +1 -1
- package/dist/extension.js +178 -412
- package/dist/extension.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/prompt.d.ts +1 -1
- package/dist/prompt.js +1 -7
- package/dist/prompt.js.map +1 -1
- package/dist/runtime/baseline.d.ts +6 -0
- package/dist/runtime/baseline.js +145 -0
- package/dist/runtime/baseline.js.map +1 -0
- package/dist/runtime/index.d.ts +2 -1
- package/dist/runtime/index.js +72 -29
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/outcome.d.ts +1 -2
- package/dist/runtime/outcome.js +21 -35
- package/dist/runtime/outcome.js.map +1 -1
- package/dist/runtime/renderers.js +34 -70
- package/dist/runtime/renderers.js.map +1 -1
- package/dist/runtime/verification.d.ts +15 -0
- package/dist/runtime/verification.js +97 -0
- package/dist/runtime/verification.js.map +1 -0
- package/dist/schema.d.ts +12 -2
- package/dist/schema.js +6 -8
- package/dist/schema.js.map +1 -1
- package/package.json +6 -6
- package/skills/create-harness/SKILL.md +19 -62
- package/skills/execute-harness/SKILL.md +27 -66
- package/skills/review-harness/SKILL.md +21 -54
package/dist/extension.js
CHANGED
|
@@ -1,308 +1,92 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
|
-
import { HARNESS_ASSESSMENT_CUSTOM_TYPE, assessment_active, assessment_context,
|
|
3
|
+
import { HARNESS_ASSESSMENT_CUSTOM_TYPE, assessment_active, assessment_context, create_assessment_state, format_assessment_record, harness_assess_params_schema, harness_assessment_submit_params_schema, submit_assessment_record, } from './assessment.js';
|
|
4
4
|
import { check_command_allowed, check_path_allowed, } from './enforcement/policy.js';
|
|
5
5
|
import { HARNESS_SYSTEM_PROMPT } from './prompt.js';
|
|
6
|
-
import { harness_paths, read_contract, read_status, } from './runtime/files.js';
|
|
6
|
+
import { harness_paths, read_contract, read_status, shell_quote, } from './runtime/files.js';
|
|
7
7
|
import { active_harness_context, amend_harness_runtime, create_harness_runtime, format_harness_status_line, format_harness_summary, update_harness_runtime, } from './runtime/index.js';
|
|
8
|
+
import { resolve_harness_cwd } from './runtime/verification.js';
|
|
8
9
|
import { HARNESS_CUSTOM_TYPE, harness_amend_params_schema, harness_create_params_schema, harness_read_params_schema, harness_update_params_schema, } from './schema.js';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
function restore_active_harness(ctx) {
|
|
15
|
-
const entries = ctx.sessionManager.getEntries();
|
|
16
|
-
return entries
|
|
17
|
-
.filter((entry) => entry.type === 'custom' &&
|
|
18
|
-
entry.customType === HARNESS_CUSTOM_TYPE)
|
|
19
|
-
.pop()?.data?.active_harness_dir;
|
|
20
|
-
}
|
|
21
|
-
function restore_assessment(ctx) {
|
|
22
|
-
const entries = ctx.sessionManager.getBranch();
|
|
23
|
-
return entries
|
|
24
|
-
.filter((entry) => entry.type === 'custom' &&
|
|
25
|
-
entry.customType === HARNESS_ASSESSMENT_CUSTOM_TYPE)
|
|
26
|
-
.pop()?.data;
|
|
27
|
-
}
|
|
28
|
-
function update_harness_ui(ctx, harness_dir) {
|
|
29
|
-
if (!harness_dir ||
|
|
30
|
-
!existsSync(harness_paths(harness_dir).contract)) {
|
|
31
|
-
ctx.ui.setStatus('harness', undefined);
|
|
32
|
-
ctx.ui.setWidget('harness', undefined);
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
ctx.ui.setStatus('harness', format_harness_status_line(harness_dir));
|
|
36
|
-
ctx.ui.setWidget('harness', undefined);
|
|
37
|
-
}
|
|
38
|
-
function update_assessment_ui(ctx, state) {
|
|
39
|
-
if (!state || !assessment_active(state)) {
|
|
40
|
-
ctx.ui.setStatus('harness-assessment', undefined);
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
const label = state.status === 'awaiting_approval'
|
|
44
|
-
? '🔎 assess: approval'
|
|
45
|
-
: '🔎 assess';
|
|
46
|
-
ctx.ui.setStatus('harness-assessment', label);
|
|
47
|
-
}
|
|
48
|
-
function parse_command(args) {
|
|
49
|
-
const [command = 'status', ...rest] = args.trim().split(/\s+/);
|
|
50
|
-
return { command, rest: rest.join(' ').trim() };
|
|
51
|
-
}
|
|
52
|
-
function tool_text(text) {
|
|
53
|
-
return { content: [{ type: 'text', text }], details: {} };
|
|
54
|
-
}
|
|
10
|
+
const tool_text = (text) => ({
|
|
11
|
+
content: [{ type: 'text', text }],
|
|
12
|
+
details: {},
|
|
13
|
+
});
|
|
55
14
|
export function should_inject_harness_prompt(event) {
|
|
56
|
-
const
|
|
57
|
-
return !
|
|
15
|
+
const selected = event.systemPromptOptions?.selectedTools;
|
|
16
|
+
return !selected || selected.includes('bash');
|
|
58
17
|
}
|
|
59
|
-
function
|
|
60
|
-
if (!harness_dir ||
|
|
61
|
-
!existsSync(harness_paths(harness_dir).status)) {
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
const status = read_status(harness_dir).status;
|
|
65
|
-
return status === 'completed' || status === 'failed';
|
|
66
|
-
}
|
|
67
|
-
export default async function harness(pi) {
|
|
18
|
+
export default function harness(pi) {
|
|
68
19
|
let active_harness_dir;
|
|
69
|
-
let
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
function persist_assessment(state, ctx) {
|
|
77
|
-
assessment_state = state;
|
|
78
|
-
pi.appendEntry(HARNESS_ASSESSMENT_CUSTOM_TYPE, state);
|
|
79
|
-
update_assessment_ui(ctx, state);
|
|
20
|
+
let assessment;
|
|
21
|
+
function update_ui(ctx) {
|
|
22
|
+
ctx.ui.setStatus('harness', active_harness_dir &&
|
|
23
|
+
existsSync(harness_paths(active_harness_dir).status)
|
|
24
|
+
? format_harness_status_line(active_harness_dir)
|
|
25
|
+
: undefined);
|
|
26
|
+
ctx.ui.setStatus('harness-assessment', assessment_active(assessment) ? '🔎 assess' : undefined);
|
|
80
27
|
}
|
|
81
|
-
function
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return assessment_state;
|
|
86
|
-
}
|
|
87
|
-
const tools_before_assessment = assessment_active(assessment_state)
|
|
88
|
-
? assessment_state?.tools_before_assessment
|
|
89
|
-
: pi.getActiveTools();
|
|
90
|
-
const state = create_assessment_state(normalized_task, source, tools_before_assessment ?? pi.getActiveTools());
|
|
91
|
-
persist_assessment(state, ctx);
|
|
92
|
-
pi.setActiveTools(assessment_tool_names(state.tools_before_assessment));
|
|
93
|
-
return state;
|
|
28
|
+
function set_active(dir, ctx) {
|
|
29
|
+
active_harness_dir = dir;
|
|
30
|
+
pi.appendEntry(HARNESS_CUSTOM_TYPE, { active_harness_dir: dir });
|
|
31
|
+
update_ui(ctx);
|
|
94
32
|
}
|
|
95
|
-
function
|
|
96
|
-
|
|
33
|
+
function start_assessment(task, ctx) {
|
|
34
|
+
assessment = create_assessment_state(task, 'user');
|
|
35
|
+
pi.appendEntry(HARNESS_ASSESSMENT_CUSTOM_TYPE, assessment);
|
|
36
|
+
update_ui(ctx);
|
|
37
|
+
return assessment;
|
|
97
38
|
}
|
|
98
|
-
function
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
current.status !== 'awaiting_approval' ||
|
|
103
|
-
!record) {
|
|
104
|
-
ctx.ui.notify('No assessment is awaiting approval', 'warning');
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
if (decision === 'direct' &&
|
|
108
|
-
active_harness_dir &&
|
|
109
|
-
!is_terminal_harness(active_harness_dir)) {
|
|
110
|
-
ctx.ui.notify('Direct work cannot bypass the active harness; submit an approved harness contract to amend it', 'warning');
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
if (decision === 'harness') {
|
|
114
|
-
if (!record.proposed_contract) {
|
|
115
|
-
ctx.ui.notify('This assessment has no proposed harness contract', 'warning');
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
const approved = {
|
|
119
|
-
...current,
|
|
120
|
-
status: 'approved',
|
|
121
|
-
decision,
|
|
122
|
-
};
|
|
123
|
-
if (active_harness_dir &&
|
|
124
|
-
!is_terminal_harness(active_harness_dir)) {
|
|
125
|
-
if (record.proposed_contract.cwd ||
|
|
126
|
-
record.proposed_contract.forbidden_paths?.length ||
|
|
127
|
-
record.proposed_contract.forbidden_commands?.length) {
|
|
128
|
-
ctx.ui.notify('Outer-policy changes require a separate harness contract', 'warning');
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
const contract = amend_harness_runtime({
|
|
132
|
-
harness_dir: active_harness_dir,
|
|
133
|
-
reason: `Approved assessment ${approved.id} v${approved.version}`,
|
|
134
|
-
requested_by: 'user',
|
|
135
|
-
task: record.proposed_contract.task,
|
|
136
|
-
allowed_paths: record.proposed_contract.allowed_paths,
|
|
137
|
-
validation_commands: record.proposed_contract.validation_commands,
|
|
138
|
-
allow_test_changes: record.proposed_contract.allow_test_changes,
|
|
139
|
-
escalation_rules: record.proposed_contract.escalation_rules,
|
|
140
|
-
planner_model: record.proposed_contract.planner_model,
|
|
141
|
-
planner_thinking: record.proposed_contract.planner_thinking,
|
|
142
|
-
executor_model: record.proposed_contract.executor_model,
|
|
143
|
-
executor_thinking: record.proposed_contract.executor_thinking,
|
|
144
|
-
reviewer_model: record.proposed_contract.reviewer_model,
|
|
145
|
-
reviewer_thinking: record.proposed_contract.reviewer_thinking,
|
|
146
|
-
});
|
|
147
|
-
persist_assessment(approved, ctx);
|
|
148
|
-
restore_tools_after_assessment(approved);
|
|
149
|
-
set_active_harness(active_harness_dir, ctx);
|
|
150
|
-
return {
|
|
151
|
-
message: `The user approved assessment ${approved.id} v${approved.version}. Resume harness ${contract.id} with approved scaffold v${contract.scaffold.version} using the execute-harness skill. Harness approval does not authorize Factory; use Factory only after a direct user request to evaluate it.`,
|
|
152
|
-
trigger: true,
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
persist_assessment(approved, ctx);
|
|
156
|
-
restore_tools_after_assessment(approved);
|
|
157
|
-
const { harness_dir, contract } = create_harness_runtime(record.proposed_contract, ctx.cwd);
|
|
158
|
-
set_active_harness(harness_dir, ctx);
|
|
159
|
-
return {
|
|
160
|
-
message: `The user approved assessment ${approved.id} v${approved.version}. Execute harness ${contract.id} from ${harness_dir} using the execute-harness skill. Harness approval does not authorize Factory; use Factory only after a direct user request to evaluate it.`,
|
|
161
|
-
trigger: true,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
const completed = {
|
|
165
|
-
...current,
|
|
166
|
-
status: decision === 'reject' ? 'rejected' : 'approved',
|
|
167
|
-
decision,
|
|
168
|
-
};
|
|
169
|
-
persist_assessment(completed, ctx);
|
|
170
|
-
restore_tools_after_assessment(completed);
|
|
171
|
-
if (decision === 'direct') {
|
|
172
|
-
return {
|
|
173
|
-
message: `The user approved direct work from assessment ${completed.id} v${completed.version}. Proceed only with the smallest vertical slice recorded in the assessment.`,
|
|
174
|
-
trigger: true,
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
return {
|
|
178
|
-
message: `The user rejected assessment ${completed.id} v${completed.version}. Do not implement the candidate work.`,
|
|
179
|
-
trigger: false,
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
function send_decision_message(result) {
|
|
183
|
-
pi.sendMessage({
|
|
184
|
-
customType: 'harness-assessment-decision',
|
|
185
|
-
content: result.message,
|
|
186
|
-
display: true,
|
|
187
|
-
}, {
|
|
188
|
-
triggerTurn: result.trigger,
|
|
189
|
-
deliverAs: 'followUp',
|
|
190
|
-
});
|
|
39
|
+
function require_active(dir) {
|
|
40
|
+
if (!active_harness_dir ||
|
|
41
|
+
resolve(dir) !== resolve(active_harness_dir))
|
|
42
|
+
throw new Error('Use /harness use <dir> to select this harness first');
|
|
191
43
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const harness_is_active = active_harness_dir &&
|
|
203
|
-
!is_terminal_harness(active_harness_dir);
|
|
204
|
-
const options = [
|
|
205
|
-
...(current.record.proposed_contract
|
|
206
|
-
? [
|
|
207
|
-
harness_is_active
|
|
208
|
-
? 'Approve and amend active harness'
|
|
209
|
-
: 'Create and execute harness',
|
|
210
|
-
]
|
|
211
|
-
: []),
|
|
212
|
-
...(harness_is_active ? [] : ['Proceed with direct work']),
|
|
213
|
-
'Reject the proposed work',
|
|
214
|
-
'Keep assessing',
|
|
215
|
-
];
|
|
216
|
-
const choice = await ctx.ui.select(`Assessment ${current.id} v${current.version}`, options);
|
|
217
|
-
if (assessment_state?.id !== current.id ||
|
|
218
|
-
assessment_state.version !== current.version) {
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
const decision = choice?.startsWith('Create') || choice?.startsWith('Approve')
|
|
222
|
-
? 'harness'
|
|
223
|
-
: choice?.startsWith('Proceed')
|
|
224
|
-
? 'direct'
|
|
225
|
-
: choice?.startsWith('Reject')
|
|
226
|
-
? 'reject'
|
|
227
|
-
: undefined;
|
|
228
|
-
if (!decision)
|
|
229
|
-
return;
|
|
230
|
-
const result = complete_assessment(decision, ctx);
|
|
231
|
-
if (result)
|
|
232
|
-
send_decision_message(result);
|
|
233
|
-
}
|
|
234
|
-
finally {
|
|
235
|
-
decision_prompt_open = false;
|
|
44
|
+
function restore(ctx) {
|
|
45
|
+
active_harness_dir = undefined;
|
|
46
|
+
assessment = undefined;
|
|
47
|
+
for (const entry of ctx.sessionManager.getBranch()) {
|
|
48
|
+
if (entry.type !== 'custom')
|
|
49
|
+
continue;
|
|
50
|
+
if (entry.customType === HARNESS_CUSTOM_TYPE)
|
|
51
|
+
active_harness_dir = entry.data?.active_harness_dir;
|
|
52
|
+
if (entry.customType === HARNESS_ASSESSMENT_CUSTOM_TYPE)
|
|
53
|
+
assessment = entry.data;
|
|
236
54
|
}
|
|
55
|
+
if (active_harness_dir &&
|
|
56
|
+
existsSync(harness_paths(active_harness_dir).status) &&
|
|
57
|
+
read_status(active_harness_dir).status === 'completed')
|
|
58
|
+
set_active(undefined, ctx);
|
|
59
|
+
update_ui(ctx);
|
|
237
60
|
}
|
|
238
61
|
pi.registerCommand('assess', {
|
|
239
|
-
description: '
|
|
62
|
+
description: 'Optional investigation and recommendation; does not lock tools or create an approval gate',
|
|
240
63
|
handler: async (args, ctx) => {
|
|
241
|
-
const
|
|
242
|
-
if (!
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
ctx.ui.notify(format_assessment_record(assessment_state), 'info');
|
|
248
|
-
return;
|
|
64
|
+
const task = args.trim();
|
|
65
|
+
if (!task || task === 'status') {
|
|
66
|
+
ctx.ui.notify(assessment
|
|
67
|
+
? format_assessment_record(assessment)
|
|
68
|
+
: 'No assessment', 'info');
|
|
249
69
|
}
|
|
250
|
-
if (
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (result)
|
|
255
|
-
send_decision_message(result);
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
await prompt_for_assessment_decision(ctx);
|
|
259
|
-
return;
|
|
70
|
+
else if (task === 'clear') {
|
|
71
|
+
assessment = undefined;
|
|
72
|
+
pi.appendEntry(HARNESS_ASSESSMENT_CUSTOM_TYPE, undefined);
|
|
73
|
+
update_ui(ctx);
|
|
260
74
|
}
|
|
261
|
-
if (
|
|
262
|
-
|
|
263
|
-
if (result)
|
|
264
|
-
send_decision_message(result);
|
|
265
|
-
return;
|
|
75
|
+
else if (/^(approve|reject)(?:\s|$)/.test(task)) {
|
|
76
|
+
ctx.ui.notify('Assessment is advisory. No approval roundtrip is required; use /harness create when useful.', 'info');
|
|
266
77
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
restore_tools_after_assessment(assessment_state);
|
|
270
|
-
}
|
|
271
|
-
assessment_state = undefined;
|
|
272
|
-
pi.appendEntry(HARNESS_ASSESSMENT_CUSTOM_TYPE, undefined);
|
|
273
|
-
update_assessment_ui(ctx, undefined);
|
|
274
|
-
ctx.ui.notify('Cleared harness assessment', 'info');
|
|
275
|
-
return;
|
|
78
|
+
else {
|
|
79
|
+
pi.sendUserMessage(assessment_context(start_assessment(task, ctx)));
|
|
276
80
|
}
|
|
277
|
-
const state = start_assessment(trimmed, 'user', ctx);
|
|
278
|
-
pi.sendUserMessage(`Assess this candidate work without modifying the repository. Use repository evidence and existing primitives before recommending direct work, a harness, or rejection.\n\n${state.task}`);
|
|
279
81
|
},
|
|
280
82
|
});
|
|
281
83
|
pi.registerCommand('harness', {
|
|
282
|
-
description: '
|
|
84
|
+
description: 'Create, run, review, or inspect an optional task harness',
|
|
283
85
|
handler: async (args, ctx) => {
|
|
284
|
-
const
|
|
86
|
+
const [command = 'status', ...words] = args.trim().split(/\s+/);
|
|
87
|
+
const rest = words.join(' ');
|
|
285
88
|
if (command === 'clear') {
|
|
286
|
-
|
|
287
|
-
ctx.ui.notify('Cleared active harness', 'info');
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
if (command === 'use') {
|
|
291
|
-
if (!rest) {
|
|
292
|
-
ctx.ui.notify('Usage: /harness use <dir>', 'warning');
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
set_active_harness(rest, ctx);
|
|
296
|
-
ctx.ui.notify(`Active harness: ${rest}`, 'info');
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
if (command === 'status') {
|
|
300
|
-
const harness_dir = rest || active_harness_dir;
|
|
301
|
-
if (!harness_dir) {
|
|
302
|
-
ctx.ui.notify('No active harness', 'warning');
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
ctx.ui.notify(format_harness_summary(harness_dir), 'info');
|
|
89
|
+
set_active(undefined, ctx);
|
|
306
90
|
return;
|
|
307
91
|
}
|
|
308
92
|
if (command === 'create') {
|
|
@@ -310,203 +94,185 @@ export default async function harness(pi) {
|
|
|
310
94
|
ctx.ui.notify('Usage: /harness create <task>', 'warning');
|
|
311
95
|
return;
|
|
312
96
|
}
|
|
313
|
-
|
|
314
|
-
pi.sendUserMessage(`
|
|
97
|
+
set_active(undefined, ctx);
|
|
98
|
+
pi.sendUserMessage(`Create a task harness with harness_create for: ${rest}. Use the project's normal acceptance checks and a suitable scope. No assessment or extra approval roundtrip is required. Do not expand the user-authorized task.`);
|
|
315
99
|
return;
|
|
316
100
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
ctx.ui.notify('Usage: /harness run <dir>', 'warning');
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
set_active_harness(harness_dir, ctx);
|
|
324
|
-
pi.sendUserMessage(`Use the execute-harness skill to run this harness until validation completes: ${harness_dir}. Harness execution does not authorize Factory; use Factory only after a direct user request to evaluate it. If the team tool is available, create or reuse a team and create a task for this harness. Use one mutating teammate only when useful; use a worktree only when isolation and merge ownership are explicit. Then inspect team status/results before reporting.`);
|
|
101
|
+
const dir = rest || active_harness_dir;
|
|
102
|
+
if (!dir) {
|
|
103
|
+
ctx.ui.notify('No active harness. Use /harness create <task>.', 'info');
|
|
325
104
|
return;
|
|
326
105
|
}
|
|
327
|
-
if (command === '
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
return;
|
|
106
|
+
if (command === 'use' ||
|
|
107
|
+
command === 'run' ||
|
|
108
|
+
command === 'review') {
|
|
109
|
+
if (!existsSync(harness_paths(dir).contract))
|
|
110
|
+
throw new Error('Harness contract does not exist');
|
|
111
|
+
set_active(dir, ctx);
|
|
112
|
+
if (command !== 'use')
|
|
113
|
+
pi.sendUserMessage(`Use the ${command === 'review' ? 'review' : 'execute'}-harness skill for ${dir}. Work directly unless the user has authorized peer collaboration; Team Mode only coordinates existing sessions.`);
|
|
336
114
|
}
|
|
337
|
-
|
|
115
|
+
else if (command === 'status')
|
|
116
|
+
ctx.ui.notify(format_harness_summary(dir), 'info');
|
|
117
|
+
else
|
|
118
|
+
ctx.ui.notify('Usage: /harness create|run|review|status|use|clear', 'info');
|
|
338
119
|
},
|
|
339
120
|
});
|
|
340
121
|
pi.registerTool({
|
|
341
122
|
name: 'harness_assess',
|
|
342
|
-
label: '
|
|
343
|
-
description: '
|
|
344
|
-
promptSnippet: 'Enter read-only evidence-gated assessment before adopting broad or risky work',
|
|
345
|
-
promptGuidelines: [
|
|
346
|
-
'Call harness_assess before repository mutation when a task may need an enforceable harness contract.',
|
|
347
|
-
'Call harness_assess by itself; after it activates, use only the remaining read-only tools.',
|
|
348
|
-
],
|
|
123
|
+
label: 'Assess Task',
|
|
124
|
+
description: 'Optional advisory investigation. Does not restrict tools or require another approval.',
|
|
349
125
|
parameters: harness_assess_params_schema,
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
return tool_text(assessment_context(
|
|
126
|
+
executionMode: 'sequential',
|
|
127
|
+
async execute(_id, params, _signal, _update, ctx) {
|
|
128
|
+
return tool_text(assessment_context(start_assessment(params.task, ctx)));
|
|
353
129
|
},
|
|
354
130
|
});
|
|
355
131
|
pi.registerTool({
|
|
356
132
|
name: 'harness_assessment_submit',
|
|
357
|
-
label: '
|
|
358
|
-
description: '
|
|
133
|
+
label: 'Record Assessment',
|
|
134
|
+
description: 'Record an optional recommendation; does not create or approve a harness.',
|
|
359
135
|
parameters: harness_assessment_submit_params_schema,
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
start_assessment(params.task, 'agent', ctx);
|
|
367
|
-
const submitted = submit_assessment_record(current, params);
|
|
368
|
-
persist_assessment(submitted, ctx);
|
|
369
|
-
return tool_text(`${format_assessment_record(submitted)}\n\nWaiting for direct user approval.`);
|
|
136
|
+
executionMode: 'sequential',
|
|
137
|
+
async execute(_id, params, _signal, _update, ctx) {
|
|
138
|
+
assessment = submit_assessment_record(assessment ?? create_assessment_state(params.task, 'agent'), params);
|
|
139
|
+
pi.appendEntry(HARNESS_ASSESSMENT_CUSTOM_TYPE, assessment);
|
|
140
|
+
update_ui(ctx);
|
|
141
|
+
return tool_text(format_assessment_record(assessment));
|
|
370
142
|
},
|
|
371
143
|
});
|
|
372
144
|
pi.registerTool({
|
|
373
145
|
name: 'harness_create',
|
|
374
146
|
label: 'Create Harness',
|
|
375
|
-
description: '
|
|
376
|
-
promptSnippet: 'Request constrained execution only after evidence-gated assessment and direct user approval',
|
|
377
|
-
promptGuidelines: [
|
|
378
|
-
'Use harness_assess before harness_create. An unapproved harness_create call is blocked and starts assessment.',
|
|
379
|
-
'Use the normal direct workflow for bounded, low-risk changes covered by standard validation.',
|
|
380
|
-
],
|
|
147
|
+
description: 'Create a task contract with accepted validation commands and optional scope. Assessment is not required.',
|
|
381
148
|
parameters: harness_create_params_schema,
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
149
|
+
executionMode: 'sequential',
|
|
150
|
+
async execute(_id, params, _signal, _update, ctx) {
|
|
151
|
+
if (active_harness_dir)
|
|
152
|
+
throw new Error('A harness is already active; the user can /harness clear or /harness create a new task');
|
|
153
|
+
if (params.cwd && resolve(params.cwd) !== resolve(ctx.cwd))
|
|
154
|
+
throw new Error('Harness cwd must match the current project');
|
|
155
|
+
const { harness_dir } = create_harness_runtime(params, ctx.cwd);
|
|
156
|
+
if (assessment_active(assessment)) {
|
|
157
|
+
assessment = undefined;
|
|
158
|
+
pi.appendEntry(HARNESS_ASSESSMENT_CUSTOM_TYPE, undefined);
|
|
159
|
+
}
|
|
160
|
+
set_active(harness_dir, ctx);
|
|
161
|
+
return tool_text(format_harness_summary(harness_dir));
|
|
385
162
|
},
|
|
386
163
|
});
|
|
387
164
|
pi.registerTool({
|
|
388
165
|
name: 'harness_amend',
|
|
389
|
-
label: 'Amend
|
|
390
|
-
description: '
|
|
391
|
-
promptSnippet: 'Amend bounded task scope, validation, test policy, or model strategy when evidence changes',
|
|
392
|
-
promptGuidelines: [
|
|
393
|
-
'Use harness_amend for bounded changes to the inner scaffold.',
|
|
394
|
-
'New capabilities, architecture decisions, or outer-policy expansion require assessment instead of a silent amendment.',
|
|
395
|
-
],
|
|
166
|
+
label: 'Amend Strategy',
|
|
167
|
+
description: 'Adjust the execution plan without removing accepted checks or expanding the accepted maximum scope.',
|
|
396
168
|
parameters: harness_amend_params_schema,
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
169
|
+
executionMode: 'sequential',
|
|
170
|
+
async execute(_id, params, _signal, _update, ctx) {
|
|
171
|
+
require_active(params.harness_dir);
|
|
172
|
+
amend_harness_runtime({ ...params, requested_by: 'planner' });
|
|
173
|
+
update_ui(ctx);
|
|
174
|
+
return tool_text(format_harness_summary(params.harness_dir));
|
|
401
175
|
},
|
|
402
176
|
});
|
|
403
177
|
pi.registerTool({
|
|
404
178
|
name: 'harness_update',
|
|
405
179
|
label: 'Update Harness',
|
|
406
|
-
description: '
|
|
180
|
+
description: 'Record progress. Requesting completed runs accepted checks and the drift guard through normal tool hooks before recording success.',
|
|
407
181
|
parameters: harness_update_params_schema,
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
182
|
+
executionMode: 'sequential',
|
|
183
|
+
async execute(_id, params, signal, _update, ctx) {
|
|
184
|
+
require_active(params.harness_dir);
|
|
185
|
+
try {
|
|
186
|
+
const status = await update_harness_runtime(params, {
|
|
187
|
+
cwd: ctx.cwd,
|
|
188
|
+
signal,
|
|
189
|
+
run: async (command, cwd, run_signal) => {
|
|
190
|
+
const outcome = await ctx.executeTool('bash', { command: `cd -- ${shell_quote(cwd)} && ${command}` }, { signal: run_signal });
|
|
191
|
+
return {
|
|
192
|
+
passed: outcome.isError === false,
|
|
193
|
+
output: JSON.stringify(outcome.result),
|
|
194
|
+
};
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
return tool_text(`Harness ${status.status}. Evidence: ${harness_paths(params.harness_dir).outcome_markdown}`);
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
update_ui(ctx);
|
|
201
|
+
}
|
|
412
202
|
},
|
|
413
203
|
});
|
|
414
204
|
pi.registerTool({
|
|
415
205
|
name: 'harness_read',
|
|
416
206
|
label: 'Read Harness',
|
|
417
|
-
description: 'Read
|
|
207
|
+
description: 'Read the current task contract and outcome locations.',
|
|
418
208
|
parameters: harness_read_params_schema,
|
|
419
|
-
async execute(
|
|
209
|
+
async execute(_id, params) {
|
|
420
210
|
return tool_text(format_harness_summary(params.harness_dir));
|
|
421
211
|
},
|
|
422
212
|
});
|
|
423
|
-
pi.on('session_start', async (_event, ctx) =>
|
|
424
|
-
|
|
425
|
-
assessment_state = restore_assessment(ctx);
|
|
426
|
-
if (is_terminal_harness(active_harness_dir)) {
|
|
427
|
-
set_active_harness(undefined, ctx);
|
|
428
|
-
}
|
|
429
|
-
if (assessment_active(assessment_state)) {
|
|
430
|
-
pi.setActiveTools(assessment_tool_names(pi.getActiveTools()));
|
|
431
|
-
}
|
|
432
|
-
update_harness_ui(ctx, active_harness_dir);
|
|
433
|
-
update_assessment_ui(ctx, assessment_state);
|
|
434
|
-
});
|
|
213
|
+
pi.on('session_start', async (_event, ctx) => restore(ctx));
|
|
214
|
+
pi.on('session_tree', async (_event, ctx) => restore(ctx));
|
|
435
215
|
pi.on('input', async (event, ctx) => {
|
|
436
216
|
if (event.source !== 'extension' &&
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
217
|
+
active_harness_dir &&
|
|
218
|
+
existsSync(harness_paths(active_harness_dir).status) &&
|
|
219
|
+
['completed', 'failed'].includes(read_status(active_harness_dir).status))
|
|
220
|
+
set_active(undefined, ctx);
|
|
440
221
|
});
|
|
441
222
|
pi.on('before_agent_start', async (event) => {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
223
|
+
const contexts = [
|
|
224
|
+
should_inject_harness_prompt(event)
|
|
225
|
+
? HARNESS_SYSTEM_PROMPT
|
|
226
|
+
: '',
|
|
227
|
+
assessment_active(assessment) && assessment
|
|
228
|
+
? assessment_context(assessment)
|
|
229
|
+
: '',
|
|
230
|
+
active_harness_dir &&
|
|
231
|
+
existsSync(harness_paths(active_harness_dir).contract)
|
|
232
|
+
? active_harness_context(active_harness_dir)
|
|
233
|
+
: '',
|
|
234
|
+
].filter(Boolean);
|
|
235
|
+
if (contexts.length) {
|
|
236
|
+
event.systemPromptOptions.sections.harness =
|
|
237
|
+
contexts.join('\n\n');
|
|
449
238
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
system_prompt = `${system_prompt}\n\n${active_harness_context(active_harness_dir)}`;
|
|
239
|
+
else {
|
|
240
|
+
delete event.systemPromptOptions.sections.harness;
|
|
453
241
|
}
|
|
454
|
-
return { systemPrompt: system_prompt };
|
|
455
242
|
});
|
|
456
243
|
pi.on('tool_call', async (event, ctx) => {
|
|
457
|
-
if (event.toolName === 'harness_create') {
|
|
458
|
-
const input = event.input;
|
|
459
|
-
const task = input.task?.trim() || 'Unspecified harness request';
|
|
460
|
-
const state = start_assessment(task, 'harness-create', ctx);
|
|
461
|
-
return {
|
|
462
|
-
block: true,
|
|
463
|
-
reason: `Harness creation requires direct approval of an assessment. Assessment ${state.id} is active; investigate and call harness_assessment_submit.`,
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
const current_assessment = assessment_state;
|
|
467
|
-
if (current_assessment && assessment_active(current_assessment)) {
|
|
468
|
-
if (!is_assessment_tool_allowed(event.toolName)) {
|
|
469
|
-
return {
|
|
470
|
-
block: true,
|
|
471
|
-
reason: `Assessment ${current_assessment.id}: tool ${event.toolName} is not read-only and is disabled until the user decides.`,
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
if (event.toolName === 'bash') {
|
|
475
|
-
const input = event.input;
|
|
476
|
-
const result = check_assessment_command(input.command ?? '');
|
|
477
|
-
if (!result.ok) {
|
|
478
|
-
return { block: true, reason: result.reason };
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
244
|
if (!active_harness_dir ||
|
|
483
|
-
!existsSync(harness_paths(active_harness_dir).contract))
|
|
245
|
+
!existsSync(harness_paths(active_harness_dir).contract))
|
|
484
246
|
return;
|
|
485
|
-
}
|
|
486
247
|
const contract = read_contract(active_harness_dir);
|
|
487
248
|
if (event.toolName === 'bash') {
|
|
488
|
-
const
|
|
489
|
-
const command = input.command ?? '';
|
|
490
|
-
const result = check_command_allowed(contract, command);
|
|
249
|
+
const result = check_command_allowed(contract, String(event.input.command ?? ''));
|
|
491
250
|
if (!result.ok)
|
|
492
251
|
return { block: true, reason: result.reason };
|
|
493
252
|
}
|
|
494
|
-
if (event.toolName === '
|
|
253
|
+
if (event.toolName === 'write' || event.toolName === 'edit') {
|
|
495
254
|
const input = event.input;
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
255
|
+
const path = input.path ?? input.file_path ?? input.filePath;
|
|
256
|
+
if (typeof path !== 'string')
|
|
257
|
+
return {
|
|
258
|
+
block: true,
|
|
259
|
+
reason: 'Harness mutation requires a known file path',
|
|
260
|
+
};
|
|
261
|
+
let cwd;
|
|
262
|
+
try {
|
|
263
|
+
cwd = resolve_harness_cwd(contract, ctx.cwd);
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
return { block: true, reason: String(error) };
|
|
267
|
+
}
|
|
268
|
+
const result = check_path_allowed({ ...contract, policy: { ...contract.policy, cwd } }, path);
|
|
499
269
|
if (!result.ok)
|
|
500
270
|
return { block: true, reason: result.reason };
|
|
501
271
|
}
|
|
502
272
|
});
|
|
503
|
-
pi.on('agent_end', async (_event, ctx) => {
|
|
504
|
-
await prompt_for_assessment_decision(ctx);
|
|
505
|
-
});
|
|
506
273
|
pi.on('session_shutdown', async (_event, ctx) => {
|
|
507
274
|
ctx.ui.setStatus('harness', undefined);
|
|
508
275
|
ctx.ui.setStatus('harness-assessment', undefined);
|
|
509
|
-
ctx.ui.setWidget('harness', undefined);
|
|
510
276
|
});
|
|
511
277
|
}
|
|
512
278
|
//# sourceMappingURL=extension.js.map
|