ultimate-pi 0.19.1 → 0.22.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/.agents/skills/harness-decisions/SKILL.md +68 -2
- package/.agents/skills/harness-git-commit/SKILL.md +72 -0
- package/.agents/skills/harness-governor/SKILL.md +2 -2
- package/.agents/skills/harness-ls-lint-setup/SKILL.md +59 -0
- package/.agents/skills/harness-plan/SKILL.md +13 -11
- package/.agents/skills/harness-review/SKILL.md +1 -1
- package/.agents/skills/harness-sentrux-repair/SKILL.md +48 -0
- package/.agents/skills/sentrux/SKILL.md +4 -2
- package/.agents/skills/wiki-save/SKILL.md +1 -1
- package/.pi/PACKAGING.md +6 -0
- package/.pi/SYSTEM.md +21 -3
- package/.pi/agents/harness/ls-lint-steward.md +49 -0
- package/.pi/agents/harness/planning/decompose.md +4 -4
- package/.pi/agents/harness/reviewing/evaluator.md +1 -1
- package/.pi/agents/harness/running/executor.md +43 -2
- package/.pi/agents/harness/sentrux-repair-advisor.md +50 -0
- package/.pi/agents/pi-pi/prompt-expert.md +17 -2
- package/.pi/auto-commit.json +9 -2
- package/.pi/extensions/debate-orchestrator.ts +3 -0
- package/.pi/extensions/harness-anchored-edit.ts +139 -0
- package/.pi/extensions/harness-ask-user.ts +13 -34
- package/.pi/extensions/harness-debate-tools.ts +43 -4
- package/.pi/extensions/harness-live-widget.ts +28 -19
- package/.pi/extensions/harness-run-context.ts +278 -115
- package/.pi/extensions/harness-web-tools.ts +598 -471
- package/.pi/extensions/ls-lint-rules-sync.ts +103 -0
- package/.pi/extensions/observation-bus.ts +4 -0
- package/.pi/extensions/policy-gate.ts +270 -229
- package/.pi/extensions/sentrux-rules-sync.ts +2 -0
- package/.pi/extensions/soundboard.ts +48 -48
- package/.pi/harness/README.md +4 -0
- package/.pi/harness/agents.manifest.json +15 -7
- package/.pi/harness/agents.policy.yaml +47 -81
- package/.pi/harness/docs/adrs/0051-hash-anchored-executor-edits.md +41 -0
- package/.pi/harness/docs/adrs/0052-ls-lint-naming-lifecycle.md +45 -0
- package/.pi/harness/docs/adrs/0052-sentrux-structured-repair.md +38 -0
- package/.pi/harness/docs/adrs/0053-plan-task-clarification-gate.md +39 -0
- package/.pi/harness/docs/adrs/0054-harness-native-ask-user.md +40 -0
- package/.pi/harness/docs/adrs/0055-auto-commit-coauthor-lifecycle.md +40 -0
- package/.pi/harness/docs/adrs/README.md +7 -0
- package/.pi/harness/docs/practice-map.md +21 -5
- package/.pi/harness/evals/smoke/ls-lint-stub.json +10 -0
- package/.pi/harness/evolution/self-healing-rules.json +16 -0
- package/.pi/harness/ls-lint/naming.manifest.json +128 -0
- package/.pi/harness/sentrux/architecture.manifest.json +1 -1
- package/.pi/harness/specs/auto-commit.schema.json +63 -0
- package/.pi/harness/specs/ls-lint-manifest-proposal.schema.json +80 -0
- package/.pi/harness/specs/ls-lint-signal.schema.json +47 -0
- package/.pi/harness/specs/naming-manifest.schema.json +54 -0
- package/.pi/harness/specs/plan-task-clarification.schema.json +88 -0
- package/.pi/harness/specs/sentrux-diagnostics.schema.json +173 -0
- package/.pi/harness/specs/sentrux-repair-plan.schema.json +133 -0
- package/.pi/harness/specs/sentrux-report.schema.json +119 -0
- package/.pi/harness/specs/sentrux-signal.schema.json +34 -1
- package/.pi/lib/agents-policy.d.mts +26 -47
- package/.pi/lib/agents-policy.mjs +84 -29
- package/.pi/lib/agents-policy.ts +1 -0
- package/.pi/lib/agt/build-evaluation-context.ts +136 -64
- package/.pi/lib/ask-user/constants.mjs +3 -0
- package/.pi/lib/ask-user/constants.ts +4 -0
- package/.pi/lib/ask-user/contracts/glimpse-parse.ts +56 -0
- package/.pi/lib/ask-user/contracts/glimpse-payload-build.ts +58 -0
- package/.pi/lib/ask-user/contracts/glimpse-payload.ts +38 -0
- package/.pi/lib/ask-user/core/questionnaire.ts +74 -0
- package/.pi/lib/ask-user/dialog.ts +2 -314
- package/.pi/lib/ask-user/fallback.ts +2 -78
- package/.pi/lib/ask-user/format.ts +85 -0
- package/.pi/lib/ask-user/glimpseui.d.ts +10 -0
- package/.pi/lib/ask-user/index.ts +114 -0
- package/.pi/lib/ask-user/merge-task-clarification.ts +98 -0
- package/.pi/lib/ask-user/policy.mjs +43 -0
- package/.pi/lib/ask-user/policy.ts +104 -0
- package/.pi/lib/ask-user/presenters/glimpse.ts +130 -0
- package/.pi/lib/ask-user/presenters/headless.ts +131 -0
- package/.pi/lib/ask-user/presenters/select.ts +60 -0
- package/.pi/lib/ask-user/presenters/tui.ts +373 -0
- package/.pi/lib/ask-user/presenters/types.ts +13 -0
- package/.pi/lib/ask-user/render.ts +40 -9
- package/.pi/lib/ask-user/schema.ts +66 -13
- package/.pi/lib/ask-user/types.ts +60 -3
- package/.pi/lib/ask-user/validate-core.mjs +193 -7
- package/.pi/lib/ask-user/validate.ts +53 -34
- package/.pi/lib/harness-anchored-edit/.hash_anchors +1721 -0
- package/.pi/lib/harness-anchored-edit/anchor-state.ts +320 -0
- package/.pi/lib/harness-anchored-edit/apply-anchored-edits.ts +161 -0
- package/.pi/lib/harness-anchored-edit/edit-executor.ts +146 -0
- package/.pi/lib/harness-anchored-edit/index.ts +9 -0
- package/.pi/lib/harness-anchored-edit/line-protocol.ts +38 -0
- package/.pi/lib/harness-anchored-edit/package.json +3 -0
- package/.pi/lib/harness-anchored-edit/settings.ts +1 -0
- package/.pi/lib/harness-anchored-edit/task-id.ts +8 -0
- package/.pi/lib/harness-anchored-edit/types.ts +19 -0
- package/.pi/lib/harness-artifact-gate.ts +75 -21
- package/.pi/lib/harness-auto-commit-config.mjs +321 -0
- package/.pi/lib/harness-lens/clients/anchored-edit-autopatch.ts +158 -0
- package/.pi/lib/harness-lens/clients/lsp/client.ts +62 -39
- package/.pi/lib/harness-lens/clients/tool-policy.ts +73 -181
- package/.pi/lib/harness-lens/index.ts +246 -96
- package/.pi/lib/harness-lens/tools/lsp-navigation.ts +10 -8
- package/.pi/lib/harness-repair-brief.ts +84 -25
- package/.pi/lib/harness-run-context.ts +42 -52
- package/.pi/lib/harness-sentrux-parse.mjs +272 -0
- package/.pi/lib/harness-sentrux-root.mjs +78 -0
- package/.pi/lib/harness-slash-completions.ts +116 -0
- package/.pi/lib/harness-spawn-topology.ts +121 -87
- package/.pi/lib/harness-subagent-submit-registry.ts +10 -0
- package/.pi/lib/harness-subagents-bridge.ts +11 -6
- package/.pi/lib/harness-ui-state.ts +95 -48
- package/.pi/lib/plan-approval/dialog.ts +5 -0
- package/.pi/lib/plan-approval/validate.ts +1 -1
- package/.pi/lib/plan-approval-readiness.ts +32 -0
- package/.pi/lib/plan-debate-gate.ts +154 -114
- package/.pi/lib/plan-task-clarification.ts +158 -0
- package/.pi/prompts/harness-auto.md +2 -2
- package/.pi/prompts/harness-ls-lint-steward.md +43 -0
- package/.pi/prompts/harness-plan.md +58 -8
- package/.pi/prompts/harness-review.md +40 -6
- package/.pi/prompts/harness-run.md +33 -11
- package/.pi/prompts/harness-setup.md +72 -3
- package/.pi/prompts/harness-steer.md +3 -2
- package/.pi/prompts/wiki-save.md +5 -4
- package/.pi/scripts/README.md +8 -0
- package/.pi/scripts/generate-agents-policy-yaml.mjs +14 -2
- package/.pi/scripts/harness-anchored-edit-smoke.mjs +45 -0
- package/.pi/scripts/harness-auto-commit-bootstrap.mjs +96 -0
- package/.pi/scripts/harness-cli-verify.sh +47 -0
- package/.pi/scripts/harness-git-churn.mjs +77 -0
- package/.pi/scripts/harness-git-commit.mjs +173 -0
- package/.pi/scripts/harness-ls-lint-bootstrap.mjs +142 -0
- package/.pi/scripts/harness-ls-lint-cli.mjs +184 -0
- package/.pi/scripts/harness-seed-project-contracts.mjs +47 -0
- package/.pi/scripts/harness-sentrux-diagnostics.mjs +230 -0
- package/.pi/scripts/harness-sentrux-report.mjs +256 -0
- package/.pi/scripts/harness-verify.mjs +347 -117
- package/.pi/scripts/ls-lint-rules-sync.mjs +265 -0
- package/.pi/scripts/run-tests.mjs +65 -0
- package/.pi/settings.example.json +1 -0
- package/.sentrux/rules.toml +1 -1
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +31 -0
- package/README.md +13 -4
- package/THIRD_PARTY_NOTICES.md +7 -0
- package/package.json +8 -3
- package/vendor/pi-subagents/src/agents.ts +5 -0
- package/vendor/pi-subagents/src/subagents.ts +22 -3
- package/vendor/pi-vcc/src/hooks/before-compact.ts +86 -60
- package/.pi/scripts/release.sh +0 -338
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* harness-verify — deterministic harness contract checks (no LLM).
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { readFile, access } from "node:fs/promises";
|
|
6
|
+
import { readFile, access, readdir } from "node:fs/promises";
|
|
7
7
|
import { constants } from "node:fs";
|
|
8
8
|
import { join, dirname } from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
@@ -22,9 +22,17 @@ const REQUIRED_SCHEMAS = [
|
|
|
22
22
|
"run-trace.schema.json",
|
|
23
23
|
"eval-verdict.schema.json",
|
|
24
24
|
"harness-spawn-context.schema.json",
|
|
25
|
+
"plan-task-clarification.schema.json",
|
|
25
26
|
"harness-turn.schema.json",
|
|
26
27
|
"sentrux-manifest-proposal.schema.json",
|
|
28
|
+
"sentrux-report.schema.json",
|
|
29
|
+
"sentrux-diagnostics.schema.json",
|
|
30
|
+
"sentrux-repair-plan.schema.json",
|
|
27
31
|
"sentrux-signal.schema.json",
|
|
32
|
+
"naming-manifest.schema.json",
|
|
33
|
+
"ls-lint-manifest-proposal.schema.json",
|
|
34
|
+
"ls-lint-signal.schema.json",
|
|
35
|
+
"auto-commit.schema.json",
|
|
28
36
|
];
|
|
29
37
|
|
|
30
38
|
const REQUIRED_ADRS = [
|
|
@@ -46,6 +54,17 @@ const REQUIRED_ADRS = [
|
|
|
46
54
|
"0046-agt-policy-engine.md",
|
|
47
55
|
"0047-agt-layered-security.md",
|
|
48
56
|
"0048-tool-call-hook-order.md",
|
|
57
|
+
"0052-ls-lint-naming-lifecycle.md",
|
|
58
|
+
"0054-harness-native-ask-user.md",
|
|
59
|
+
"0055-auto-commit-coauthor-lifecycle.md",
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
const ASK_USER_PUBLIC_EXPORTS = [
|
|
63
|
+
"runAskUser",
|
|
64
|
+
"validateAskParams",
|
|
65
|
+
"formatResultText",
|
|
66
|
+
"isPlanApprovalAskUser",
|
|
67
|
+
"applyAskUserToTaskClarification",
|
|
49
68
|
];
|
|
50
69
|
|
|
51
70
|
const REQUIRED_EXTENSIONS = [
|
|
@@ -55,6 +74,7 @@ const REQUIRED_EXTENSIONS = [
|
|
|
55
74
|
"observation-bus.ts",
|
|
56
75
|
"drift-monitor.ts",
|
|
57
76
|
"sentrux-rules-sync.ts",
|
|
77
|
+
"ls-lint-rules-sync.ts",
|
|
58
78
|
"harness-subagents.ts",
|
|
59
79
|
];
|
|
60
80
|
|
|
@@ -68,6 +88,14 @@ const SENTRUX_MANIFEST = join(
|
|
|
68
88
|
"architecture.manifest.json",
|
|
69
89
|
);
|
|
70
90
|
const SENTRUX_RULES = join(ROOT, ".sentrux", "rules.toml");
|
|
91
|
+
const LS_LINT_MANIFEST = join(
|
|
92
|
+
ROOT,
|
|
93
|
+
".pi",
|
|
94
|
+
"harness",
|
|
95
|
+
"ls-lint",
|
|
96
|
+
"naming.manifest.json",
|
|
97
|
+
);
|
|
98
|
+
const LS_LINT_YML = join(ROOT, ".ls-lint.yml");
|
|
71
99
|
|
|
72
100
|
function fail(msg) {
|
|
73
101
|
console.error(`harness:verify FAIL: ${msg}`);
|
|
@@ -129,6 +157,60 @@ async function runNodeScript(scriptPath, args = []) {
|
|
|
129
157
|
});
|
|
130
158
|
}
|
|
131
159
|
|
|
160
|
+
const PROMPT_EXCLUDE = new Set(["release.md"]);
|
|
161
|
+
|
|
162
|
+
function parsePromptFrontmatter(raw) {
|
|
163
|
+
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
164
|
+
if (!match) return null;
|
|
165
|
+
const fields = {};
|
|
166
|
+
for (const line of match[1].split("\n")) {
|
|
167
|
+
const idx = line.indexOf(":");
|
|
168
|
+
if (idx === -1) continue;
|
|
169
|
+
const key = line.slice(0, idx).trim();
|
|
170
|
+
let value = line.slice(idx + 1).trim();
|
|
171
|
+
if (
|
|
172
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
173
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
174
|
+
) {
|
|
175
|
+
value = value.slice(1, -1);
|
|
176
|
+
}
|
|
177
|
+
fields[key] = value;
|
|
178
|
+
}
|
|
179
|
+
return fields;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function checkPromptFrontmatter() {
|
|
183
|
+
const promptsDir = join(ROOT, ".pi", "prompts");
|
|
184
|
+
const names = await readdir(promptsDir);
|
|
185
|
+
for (const name of names) {
|
|
186
|
+
if (!name.endsWith(".md") || PROMPT_EXCLUDE.has(name)) continue;
|
|
187
|
+
const path = join(promptsDir, name);
|
|
188
|
+
const raw = await readFile(path, "utf-8");
|
|
189
|
+
const fm = parsePromptFrontmatter(raw);
|
|
190
|
+
if (!fm) fail(`prompt ${name}: missing YAML frontmatter`);
|
|
191
|
+
const description = fm.description?.trim();
|
|
192
|
+
if (!description) fail(`prompt ${name}: missing or empty description`);
|
|
193
|
+
if (Object.hasOwn(fm, "argument-hint") && fm["argument-hint"] === "") {
|
|
194
|
+
fail(`prompt ${name}: argument-hint must be omitted or non-empty (not "")`);
|
|
195
|
+
}
|
|
196
|
+
const body = raw.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
|
|
197
|
+
const usesArgs = /\$ARGUMENTS|\$1|\$2|\$@/.test(body);
|
|
198
|
+
const stepZeroFlags =
|
|
199
|
+
/Step 0 — Parse arguments/.test(body) &&
|
|
200
|
+
/\[--[^\]]+\]/.test(body) &&
|
|
201
|
+
name !== "harness-run.md";
|
|
202
|
+
if (
|
|
203
|
+
(usesArgs || stepZeroFlags) &&
|
|
204
|
+
!fm["argument-hint"]?.trim()
|
|
205
|
+
) {
|
|
206
|
+
fail(
|
|
207
|
+
`prompt ${name}: requires argument-hint (uses $ARGUMENTS/$1 or Step 0 flags)`,
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
ok(`prompt frontmatter ${name}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
132
214
|
async function checkSentruxRules() {
|
|
133
215
|
if (!(await fileExists(SENTRUX_MANIFEST))) {
|
|
134
216
|
fail("missing .pi/harness/sentrux/architecture.manifest.json");
|
|
@@ -207,6 +289,127 @@ async function checkHarnessLens(pkgJson) {
|
|
|
207
289
|
ok("no harness-lens UPSTREAM_PIN.md");
|
|
208
290
|
}
|
|
209
291
|
|
|
292
|
+
async function checkHarnessAnchoredEdit(pkgJson) {
|
|
293
|
+
if (!pkgJson.files?.includes(".pi/lib/harness-anchored-edit")) {
|
|
294
|
+
fail(
|
|
295
|
+
'package.json "files" must include .pi/lib/harness-anchored-edit',
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
ok('package.json files includes .pi/lib/harness-anchored-edit');
|
|
299
|
+
|
|
300
|
+
const resolvePath = join(
|
|
301
|
+
ROOT,
|
|
302
|
+
".pi",
|
|
303
|
+
"lib",
|
|
304
|
+
"harness-anchored-edit",
|
|
305
|
+
"resolve-to-pi-edit.ts",
|
|
306
|
+
);
|
|
307
|
+
if (await fileExists(resolvePath)) {
|
|
308
|
+
fail("resolve-to-pi-edit.ts must not exist (native anchored apply)");
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const applyPath = join(
|
|
312
|
+
ROOT,
|
|
313
|
+
".pi",
|
|
314
|
+
"lib",
|
|
315
|
+
"harness-anchored-edit",
|
|
316
|
+
"apply-anchored-edits.ts",
|
|
317
|
+
);
|
|
318
|
+
if (!(await fileExists(applyPath))) {
|
|
319
|
+
fail("missing .pi/lib/harness-anchored-edit/apply-anchored-edits.ts");
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const extPath = join(ROOT, ".pi", "extensions", "harness-anchored-edit.ts");
|
|
323
|
+
if (!(await fileExists(extPath))) {
|
|
324
|
+
fail("missing .pi/extensions/harness-anchored-edit.ts");
|
|
325
|
+
}
|
|
326
|
+
const extSrc = await readFile(extPath, "utf8");
|
|
327
|
+
if (extSrc.includes("HARNESS_ANCHORED_EDIT")) {
|
|
328
|
+
fail("harness-anchored-edit must not gate on HARNESS_ANCHORED_EDIT");
|
|
329
|
+
}
|
|
330
|
+
if (extSrc.includes("createEditTool")) {
|
|
331
|
+
fail("harness-anchored-edit must not delegate to createEditTool");
|
|
332
|
+
}
|
|
333
|
+
if (extSrc.includes("resolveAnchoredInputToPiEdit")) {
|
|
334
|
+
fail("harness-anchored-edit must not use resolveAnchoredInputToPiEdit");
|
|
335
|
+
}
|
|
336
|
+
if (extSrc.includes('pi.on("tool_call"')) {
|
|
337
|
+
fail("harness-anchored-edit must not mutate edit input on tool_call");
|
|
338
|
+
}
|
|
339
|
+
if (!extSrc.includes("applyAnchoredEditsToFile")) {
|
|
340
|
+
fail("harness-anchored-edit must call applyAnchoredEditsToFile");
|
|
341
|
+
}
|
|
342
|
+
ok("harness-anchored-edit first-class contract");
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function checkLsLintRules() {
|
|
346
|
+
if (!(await fileExists(LS_LINT_MANIFEST))) {
|
|
347
|
+
fail("missing .pi/harness/ls-lint/naming.manifest.json");
|
|
348
|
+
}
|
|
349
|
+
ok("ls-lint naming.manifest.json");
|
|
350
|
+
|
|
351
|
+
const syncScript = join(ROOT, ".pi", "scripts", "ls-lint-rules-sync.mjs");
|
|
352
|
+
const { code: checkCode, out: checkOut } = await runNodeScript(syncScript, [
|
|
353
|
+
"--check",
|
|
354
|
+
]);
|
|
355
|
+
if (checkCode !== 0) {
|
|
356
|
+
fail(
|
|
357
|
+
checkOut.trim() ||
|
|
358
|
+
'.ls-lint.yml out of date — run node "$UP_PKG/.pi/scripts/ls-lint-rules-sync.mjs" --force',
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
ok(".ls-lint.yml in sync with manifest");
|
|
362
|
+
|
|
363
|
+
if (!(await fileExists(LS_LINT_YML))) {
|
|
364
|
+
fail(
|
|
365
|
+
'missing .ls-lint.yml — run node "$UP_PKG/.pi/scripts/ls-lint-rules-sync.mjs" --force',
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
ok(".ls-lint.yml present");
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async function checkLsLintGate() {
|
|
372
|
+
await checkLsLintRules();
|
|
373
|
+
|
|
374
|
+
if (process.env.HARNESS_LS_LINT_REQUIRED !== "true") {
|
|
375
|
+
ok("ls-lint signal gate skipped (HARNESS_LS_LINT_REQUIRED not set)");
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const runDir = process.env.HARNESS_RUN_DIR?.trim();
|
|
379
|
+
const runSignalPath = runDir
|
|
380
|
+
? join(runDir, "artifacts", "ls-lint-signal.yaml")
|
|
381
|
+
: null;
|
|
382
|
+
if (runSignalPath && (await fileExists(runSignalPath))) {
|
|
383
|
+
ok(`ls-lint run signal present (${runSignalPath})`);
|
|
384
|
+
} else {
|
|
385
|
+
const stubPath = join(
|
|
386
|
+
ROOT,
|
|
387
|
+
".pi",
|
|
388
|
+
"harness",
|
|
389
|
+
"evals",
|
|
390
|
+
"smoke",
|
|
391
|
+
"ls-lint-stub.json",
|
|
392
|
+
);
|
|
393
|
+
if (!(await fileExists(stubPath))) {
|
|
394
|
+
fail(
|
|
395
|
+
"HARNESS_LS_LINT_REQUIRED=true but no artifacts/ls-lint-signal.yaml and .pi/harness/evals/smoke/ls-lint-stub.json missing",
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
ok("ls-lint stub present (run signal absent — prefer artifacts/ls-lint-signal.yaml from /harness-run)");
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const cliScript = join(ROOT, ".pi", "scripts", "harness-ls-lint-cli.mjs");
|
|
402
|
+
const { code, out } = await runNodeScript(cliScript, []);
|
|
403
|
+
if (code === 127 || (out && out.includes("not installed"))) {
|
|
404
|
+
ok("ls-lint CLI check skipped (not installed)");
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (code !== 0) {
|
|
408
|
+
fail(out.trim() || "ls-lint check failed — fix path violations or update manifest");
|
|
409
|
+
}
|
|
410
|
+
ok("ls-lint check passed");
|
|
411
|
+
}
|
|
412
|
+
|
|
210
413
|
async function checkSentruxGate() {
|
|
211
414
|
await checkSentruxRules();
|
|
212
415
|
|
|
@@ -244,94 +447,73 @@ async function checkSentruxGate() {
|
|
|
244
447
|
ok("sentrux check passed");
|
|
245
448
|
}
|
|
246
449
|
|
|
247
|
-
async function
|
|
248
|
-
console.log("harness:verify\n");
|
|
249
|
-
|
|
450
|
+
async function verifySchemaAdrAndExtensions() {
|
|
250
451
|
for (const name of REQUIRED_SCHEMAS) {
|
|
251
452
|
const path = join(SPECS, name);
|
|
252
453
|
if (!(await fileExists(path))) fail(`missing schema ${name}`);
|
|
253
454
|
JSON.parse(await readFile(path, "utf-8"));
|
|
254
455
|
ok(`schema ${name}`);
|
|
255
456
|
}
|
|
256
|
-
|
|
257
457
|
for (const name of REQUIRED_ADRS) {
|
|
258
458
|
const path = join(ADRS, name);
|
|
259
459
|
if (!(await fileExists(path))) fail(`missing ADR ${name}`);
|
|
260
460
|
ok(`ADR ${name}`);
|
|
261
461
|
}
|
|
262
|
-
|
|
263
462
|
for (const name of REQUIRED_EXTENSIONS) {
|
|
264
463
|
const path = join(ROOT, ".pi", "extensions", name);
|
|
265
464
|
if (!(await fileExists(path))) fail(`missing extension ${name}`);
|
|
266
465
|
ok(`extension ${name}`);
|
|
267
466
|
}
|
|
467
|
+
}
|
|
268
468
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
469
|
+
async function verifyCoreSurfaceFiles() {
|
|
470
|
+
const required = [
|
|
471
|
+
{ path: join(ROOT, ".pi", "lib", "harness-posthog.ts"), msg: "lib/harness-posthog.ts" },
|
|
472
|
+
{ path: join(ROOT, ".pi", "lib", "harness-run-context.ts"), msg: "lib/harness-run-context.ts" },
|
|
473
|
+
{ path: join(ROOT, ".pi", "extensions", "harness-ask-user.ts"), msg: "extension harness-ask-user.ts" },
|
|
474
|
+
{ path: join(ROOT, ".pi", "lib", "harness-slash-completions.ts"), msg: "lib/harness-slash-completions.ts" },
|
|
475
|
+
];
|
|
476
|
+
for (const item of required) {
|
|
477
|
+
if (!(await fileExists(item.path))) fail(`missing ${item.msg}`);
|
|
478
|
+
ok(item.msg);
|
|
479
|
+
}
|
|
480
|
+
const askUserIndex = join(ROOT, ".pi", "lib", "ask-user", "index.ts");
|
|
481
|
+
if (!(await fileExists(askUserIndex))) fail("missing .pi/lib/ask-user/index.ts");
|
|
482
|
+
const askUserSrc = await readFile(askUserIndex, "utf-8");
|
|
483
|
+
for (const sym of ASK_USER_PUBLIC_EXPORTS) {
|
|
484
|
+
if (!askUserSrc.includes(sym)) fail(`ask-user/index.ts missing export or symbol: ${sym}`);
|
|
485
|
+
}
|
|
486
|
+
ok("ask-user public API (index.ts)");
|
|
487
|
+
}
|
|
281
488
|
|
|
489
|
+
async function verifySubagentBridgeAndGovernance(pkgJson) {
|
|
282
490
|
if (!pkgJson.files?.includes("vendor/pi-subagents")) {
|
|
283
|
-
fail(
|
|
284
|
-
'package.json "files" must include vendor/pi-subagents (npm publish ships subagents vendor)',
|
|
285
|
-
);
|
|
491
|
+
fail('package.json "files" must include vendor/pi-subagents (npm publish ships subagents vendor)');
|
|
286
492
|
}
|
|
287
493
|
ok('package.json files includes vendor/pi-subagents');
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
ROOT,
|
|
291
|
-
"vendor",
|
|
292
|
-
"pi-subagents",
|
|
293
|
-
"src",
|
|
294
|
-
"subagents.ts",
|
|
295
|
-
);
|
|
296
|
-
if (!(await fileExists(subagentsVendor))) {
|
|
297
|
-
fail("missing vendor/pi-subagents/src/subagents.ts");
|
|
298
|
-
}
|
|
494
|
+
const subagentsVendor = join(ROOT, "vendor", "pi-subagents", "src", "subagents.ts");
|
|
495
|
+
if (!(await fileExists(subagentsVendor))) fail("missing vendor/pi-subagents/src/subagents.ts");
|
|
299
496
|
const bridgePath = join(ROOT, ".pi", "lib", "harness-subagents-bridge.ts");
|
|
300
|
-
if (!(await fileExists(bridgePath)))
|
|
301
|
-
fail("missing harness-subagents-bridge.ts");
|
|
302
|
-
}
|
|
497
|
+
if (!(await fileExists(bridgePath))) fail("missing harness-subagents-bridge.ts");
|
|
303
498
|
const bridgeSrc = await readFile(bridgePath, "utf-8");
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
499
|
+
const bridgeNeedles = [
|
|
500
|
+
"precheckHarnessSubagentSpawn",
|
|
501
|
+
"packageRoot",
|
|
502
|
+
];
|
|
503
|
+
for (const needle of bridgeNeedles) {
|
|
504
|
+
if (!bridgeSrc.includes(needle)) fail(`harness-subagents-bridge missing required token: ${needle}`);
|
|
309
505
|
}
|
|
310
|
-
if (
|
|
311
|
-
|
|
312
|
-
!bridgeSrc.includes("subagentGovernanceExtensionPath")
|
|
313
|
-
) {
|
|
314
|
-
fail(
|
|
315
|
-
"harness-subagents-bridge must set subprocessGovernanceExtensionPath for all subagents",
|
|
316
|
-
);
|
|
506
|
+
if (!bridgeSrc.includes("subprocessGovernanceExtensionPath") && !bridgeSrc.includes("subagentGovernanceExtensionPath")) {
|
|
507
|
+
fail("harness-subagents-bridge must set subprocessGovernanceExtensionPath for all subagents");
|
|
317
508
|
}
|
|
318
509
|
const subagentsSrc = await readFile(subagentsVendor, "utf-8");
|
|
319
|
-
if (!subagentsSrc.includes("discoverAgents"))
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
-
if (!subagentsSrc.includes("packageRoot")) {
|
|
323
|
-
fail("vendor subagents.ts must pass packageRoot into discovery");
|
|
324
|
-
}
|
|
510
|
+
if (!subagentsSrc.includes("discoverAgents")) fail("vendor subagents.ts must implement discoverAgents");
|
|
511
|
+
if (!subagentsSrc.includes("packageRoot")) fail("vendor subagents.ts must pass packageRoot into discovery");
|
|
325
512
|
ok("vendor pi-subagents + harness bridge");
|
|
326
513
|
|
|
327
|
-
const policyGateSrc = await readFile(
|
|
328
|
-
join(ROOT, ".pi", "extensions", "policy-gate.ts"),
|
|
329
|
-
"utf-8",
|
|
330
|
-
);
|
|
514
|
+
const policyGateSrc = await readFile(join(ROOT, ".pi", "extensions", "policy-gate.ts"), "utf-8");
|
|
331
515
|
if (!policyGateSrc.includes("isPlanPhaseAllowedMutation")) {
|
|
332
|
-
fail(
|
|
333
|
-
"policy-gate.ts must use isPlanPhaseAllowedMutation (plan-phase scoped writes)",
|
|
334
|
-
);
|
|
516
|
+
fail("policy-gate.ts must use isPlanPhaseAllowedMutation (plan-phase scoped writes)");
|
|
335
517
|
}
|
|
336
518
|
if (!policyGateSrc.includes('pi.on("tool_call", async (event, ctx)')) {
|
|
337
519
|
fail("policy-gate tool_call must receive ctx for run context");
|
|
@@ -340,95 +522,143 @@ async function main() {
|
|
|
340
522
|
fail("policy-gate.ts must delegate tool_call to AGT evaluateAgtHarnessToolCall");
|
|
341
523
|
}
|
|
342
524
|
const govPath = join(ROOT, ".pi", "extensions", "subagent-governance.ts");
|
|
343
|
-
const govAlias = join(
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
"extensions",
|
|
347
|
-
"harness-subagent-governance.ts",
|
|
348
|
-
);
|
|
349
|
-
if (!(await fileExists(govPath))) {
|
|
350
|
-
fail("missing subagent-governance.ts subprocess bundle");
|
|
351
|
-
}
|
|
352
|
-
if (!(await fileExists(govAlias))) {
|
|
353
|
-
fail("missing harness-subagent-governance.ts re-export alias");
|
|
354
|
-
}
|
|
525
|
+
const govAlias = join(ROOT, ".pi", "extensions", "harness-subagent-governance.ts");
|
|
526
|
+
if (!(await fileExists(govPath))) fail("missing subagent-governance.ts subprocess bundle");
|
|
527
|
+
if (!(await fileExists(govAlias))) fail("missing harness-subagent-governance.ts re-export alias");
|
|
355
528
|
ok("policy-gate + subprocess governance");
|
|
529
|
+
}
|
|
356
530
|
|
|
531
|
+
async function runAgtPolicyDoctor() {
|
|
357
532
|
const agtDoctorPath = join(ROOT, ".pi", "scripts", "harness-agt-doctor.ts");
|
|
358
|
-
const { code: agtDoctorCode, out: agtDoctorOut } = await new Promise(
|
|
359
|
-
(
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
);
|
|
375
|
-
if (agtDoctorCode !== 0) {
|
|
376
|
-
fail(agtDoctorOut.trim() || "AGT policy doctor failed");
|
|
377
|
-
}
|
|
533
|
+
const { code: agtDoctorCode, out: agtDoctorOut } = await new Promise((resolve) => {
|
|
534
|
+
const child = spawn("npx", ["-y", "tsx", agtDoctorPath], {
|
|
535
|
+
cwd: ROOT,
|
|
536
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
537
|
+
shell: true,
|
|
538
|
+
});
|
|
539
|
+
let out = "";
|
|
540
|
+
child.stdout?.on("data", (d) => {
|
|
541
|
+
out += d.toString();
|
|
542
|
+
});
|
|
543
|
+
child.stderr?.on("data", (d) => {
|
|
544
|
+
out += d.toString();
|
|
545
|
+
});
|
|
546
|
+
child.on("close", (code) => resolve({ code: code ?? 1, out }));
|
|
547
|
+
});
|
|
548
|
+
if (agtDoctorCode !== 0) fail(agtDoctorOut.trim() || "AGT policy doctor failed");
|
|
378
549
|
ok("AGT policy doctor");
|
|
550
|
+
}
|
|
379
551
|
|
|
552
|
+
async function verifySmokeFixtures() {
|
|
380
553
|
const runCtxFixture = join(SMOKE, "run-context.fixture.json");
|
|
381
|
-
if (!(await fileExists(runCtxFixture)))
|
|
382
|
-
fail("missing run-context.fixture.json");
|
|
383
|
-
}
|
|
554
|
+
if (!(await fileExists(runCtxFixture))) fail("missing run-context.fixture.json");
|
|
384
555
|
const runCtxData = JSON.parse(await readFile(runCtxFixture, "utf-8"));
|
|
385
|
-
if (runCtxData.schema_version !== "1.0.0")
|
|
386
|
-
fail("run-context fixture schema_version must be 1.0.0");
|
|
387
|
-
}
|
|
556
|
+
if (runCtxData.schema_version !== "1.0.0") fail("run-context fixture schema_version must be 1.0.0");
|
|
388
557
|
if (!runCtxData.run_id) fail("run-context fixture missing run_id");
|
|
389
558
|
ok("run-context.fixture.json");
|
|
390
559
|
|
|
391
|
-
const fixture = JSON.parse(
|
|
392
|
-
await readFile(join(SMOKE, "run-record.fixture.json"), "utf-8"),
|
|
393
|
-
);
|
|
560
|
+
const fixture = JSON.parse(await readFile(join(SMOKE, "run-record.fixture.json"), "utf-8"));
|
|
394
561
|
validateRunRecordFixture(fixture);
|
|
395
562
|
ok("run-record.fixture.json");
|
|
396
563
|
|
|
397
|
-
const golden = JSON.parse(
|
|
398
|
-
await readFile(join(SMOKE, "test-diff-golden.json"), "utf-8"),
|
|
399
|
-
);
|
|
564
|
+
const golden = JSON.parse(await readFile(join(SMOKE, "test-diff-golden.json"), "utf-8"));
|
|
400
565
|
validateTestDiffGolden(golden);
|
|
401
566
|
ok("test-diff-golden.json");
|
|
567
|
+
}
|
|
402
568
|
|
|
403
|
-
|
|
404
|
-
|
|
569
|
+
async function verifyAgentsPolicyAndManifest() {
|
|
405
570
|
const AGENTS_POLICY = join(ROOT, ".pi", "harness", "agents.policy.yaml");
|
|
406
|
-
if (!(await fileExists(AGENTS_POLICY)))
|
|
407
|
-
fail("missing .pi/harness/agents.policy.yaml");
|
|
408
|
-
}
|
|
571
|
+
if (!(await fileExists(AGENTS_POLICY))) fail("missing .pi/harness/agents.policy.yaml");
|
|
409
572
|
ok("agents.policy.yaml present");
|
|
573
|
+
const policyYaml = await readFile(AGENTS_POLICY, "utf8");
|
|
574
|
+
if (!/^\s+extension_bundle:\s+executor/m.test(policyYaml)) {
|
|
575
|
+
fail("agents.policy.yaml kinds.executor must set extension_bundle: executor");
|
|
576
|
+
}
|
|
577
|
+
if (/harness\/running\/executor:[\s\S]*?extensions:\s+true/m.test(policyYaml)) {
|
|
578
|
+
fail("harness/running/executor must not set extensions: true (use kind extension_bundle)");
|
|
579
|
+
}
|
|
580
|
+
ok("executor extension_bundle policy");
|
|
410
581
|
|
|
411
582
|
if (!(await fileExists(AGENTS_MANIFEST))) {
|
|
412
|
-
fail(
|
|
413
|
-
"missing .pi/harness/agents.manifest.json — run node \"$UP_PKG/.pi/scripts/harness-agents-manifest.mjs\" --write",
|
|
414
|
-
);
|
|
583
|
+
fail('missing .pi/harness/agents.manifest.json — run node "$UP_PKG/.pi/scripts/harness-agents-manifest.mjs" --write');
|
|
415
584
|
}
|
|
416
585
|
ok("agents.manifest.json present");
|
|
417
|
-
|
|
418
586
|
const { code: manifestCode, out: manifestOut } = await runNodeScript(
|
|
419
587
|
join(ROOT, ".pi", "scripts", "harness-agents-manifest.mjs"),
|
|
420
588
|
["--check"],
|
|
421
589
|
);
|
|
422
|
-
if (manifestCode !== 0)
|
|
423
|
-
fail(manifestOut.trim() || "agents.manifest.json drift — regenerate with --write");
|
|
424
|
-
}
|
|
590
|
+
if (manifestCode !== 0) fail(manifestOut.trim() || "agents.manifest.json drift — regenerate with --write");
|
|
425
591
|
ok("agents.manifest.json in sync");
|
|
592
|
+
}
|
|
426
593
|
|
|
594
|
+
async function main() {
|
|
595
|
+
console.log("harness:verify\n");
|
|
596
|
+
await verifySchemaAdrAndExtensions();
|
|
597
|
+
await verifyCoreSurfaceFiles();
|
|
598
|
+
await checkPromptFrontmatter();
|
|
599
|
+
const pkgJson = JSON.parse(await readFile(join(ROOT, "package.json"), "utf-8"));
|
|
600
|
+
await checkHarnessLens(pkgJson);
|
|
601
|
+
await checkHarnessAnchoredEdit(pkgJson);
|
|
602
|
+
await verifySubagentBridgeAndGovernance(pkgJson);
|
|
603
|
+
await runAgtPolicyDoctor();
|
|
604
|
+
await verifySmokeFixtures();
|
|
605
|
+
await checkSentruxGate();
|
|
606
|
+
await checkLsLintGate();
|
|
607
|
+
await verifyAgentsPolicyAndManifest();
|
|
608
|
+
await checkAutoCommitGitCommit();
|
|
427
609
|
await checkWrsContracts();
|
|
428
|
-
|
|
429
610
|
console.log("\nharness:verify PASS");
|
|
430
611
|
}
|
|
431
612
|
|
|
613
|
+
async function checkAutoCommitGitCommit() {
|
|
614
|
+
const skill = join(
|
|
615
|
+
ROOT,
|
|
616
|
+
".agents",
|
|
617
|
+
"skills",
|
|
618
|
+
"harness-git-commit",
|
|
619
|
+
"SKILL.md",
|
|
620
|
+
);
|
|
621
|
+
const script = join(ROOT, ".pi", "scripts", "harness-git-commit.mjs");
|
|
622
|
+
const lib = join(ROOT, ".pi", "lib", "harness-auto-commit-config.mjs");
|
|
623
|
+
const bootstrap = join(
|
|
624
|
+
ROOT,
|
|
625
|
+
".pi",
|
|
626
|
+
"scripts",
|
|
627
|
+
"harness-auto-commit-bootstrap.mjs",
|
|
628
|
+
);
|
|
629
|
+
const autoCommit = join(ROOT, ".pi", "auto-commit.json");
|
|
630
|
+
for (const p of [skill, script, lib, bootstrap, autoCommit]) {
|
|
631
|
+
if (!(await fileExists(p))) {
|
|
632
|
+
fail(`missing auto-commit artifact: ${p}`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const { validateAutoCommitConfig, resolveAutoCommitConfig } = await import(
|
|
637
|
+
join(ROOT, ".pi", "lib", "harness-auto-commit-config.mjs")
|
|
638
|
+
);
|
|
639
|
+
const pkgConfig = JSON.parse(await readFile(autoCommit, "utf-8"));
|
|
640
|
+
validateAutoCommitConfig(pkgConfig);
|
|
641
|
+
await resolveAutoCommitConfig(ROOT, ROOT);
|
|
642
|
+
|
|
643
|
+
const sys = await readFile(join(ROOT, ".pi", "SYSTEM.md"), "utf-8");
|
|
644
|
+
if (!sys.includes("harness-git-commit")) {
|
|
645
|
+
fail("SYSTEM.md must reference harness-git-commit skill for commits");
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const { code, out } = await runNodeScript(script, [
|
|
649
|
+
"--print-message",
|
|
650
|
+
"--subject",
|
|
651
|
+
"harness-verify smoke",
|
|
652
|
+
]);
|
|
653
|
+
if (code !== 0) {
|
|
654
|
+
fail(out.trim() || "harness-git-commit --print-message failed");
|
|
655
|
+
}
|
|
656
|
+
if (!out.includes("Co-authored-by:")) {
|
|
657
|
+
fail("harness-git-commit message missing Co-authored-by trailer");
|
|
658
|
+
}
|
|
659
|
+
ok("auto-commit git commit (skill, CLI, config, SYSTEM.md)");
|
|
660
|
+
}
|
|
661
|
+
|
|
432
662
|
async function checkWrsContracts() {
|
|
433
663
|
const systemMd = join(ROOT, ".pi", "SYSTEM.md");
|
|
434
664
|
const toolsTs = join(ROOT, ".pi", "extensions", "harness-web-tools.ts");
|