ultimate-pi 0.20.0 → 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 +1 -1
- 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 +7 -9
- 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 +49 -82
- 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 +5 -0
- package/.pi/harness/docs/practice-map.md +10 -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 -51
- package/.pi/lib/agents-policy.mjs +41 -28
- 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/package.json +3 -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/lsp/client.ts +62 -39
- package/.pi/lib/harness-lens/clients/tool-policy.ts +73 -181
- package/.pi/lib/harness-lens/index.ts +241 -108
- 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 +4 -1
- 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 +2 -1
- 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-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 +288 -125
- package/.pi/scripts/ls-lint-rules-sync.mjs +265 -0
- package/.pi/scripts/run-tests.mjs +1 -0
- package/.pi/settings.example.json +1 -0
- package/.sentrux/rules.toml +1 -1
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +25 -0
- package/README.md +13 -4
- package/package.json +5 -1
- package/vendor/pi-vcc/src/hooks/before-compact.ts +86 -60
|
@@ -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");
|
|
@@ -260,6 +342,74 @@ async function checkHarnessAnchoredEdit(pkgJson) {
|
|
|
260
342
|
ok("harness-anchored-edit first-class contract");
|
|
261
343
|
}
|
|
262
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
|
+
|
|
263
413
|
async function checkSentruxGate() {
|
|
264
414
|
await checkSentruxRules();
|
|
265
415
|
|
|
@@ -297,95 +447,73 @@ async function checkSentruxGate() {
|
|
|
297
447
|
ok("sentrux check passed");
|
|
298
448
|
}
|
|
299
449
|
|
|
300
|
-
async function
|
|
301
|
-
console.log("harness:verify\n");
|
|
302
|
-
|
|
450
|
+
async function verifySchemaAdrAndExtensions() {
|
|
303
451
|
for (const name of REQUIRED_SCHEMAS) {
|
|
304
452
|
const path = join(SPECS, name);
|
|
305
453
|
if (!(await fileExists(path))) fail(`missing schema ${name}`);
|
|
306
454
|
JSON.parse(await readFile(path, "utf-8"));
|
|
307
455
|
ok(`schema ${name}`);
|
|
308
456
|
}
|
|
309
|
-
|
|
310
457
|
for (const name of REQUIRED_ADRS) {
|
|
311
458
|
const path = join(ADRS, name);
|
|
312
459
|
if (!(await fileExists(path))) fail(`missing ADR ${name}`);
|
|
313
460
|
ok(`ADR ${name}`);
|
|
314
461
|
}
|
|
315
|
-
|
|
316
462
|
for (const name of REQUIRED_EXTENSIONS) {
|
|
317
463
|
const path = join(ROOT, ".pi", "extensions", name);
|
|
318
464
|
if (!(await fileExists(path))) fail(`missing extension ${name}`);
|
|
319
465
|
ok(`extension ${name}`);
|
|
320
466
|
}
|
|
467
|
+
}
|
|
321
468
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
await
|
|
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
|
+
}
|
|
335
488
|
|
|
489
|
+
async function verifySubagentBridgeAndGovernance(pkgJson) {
|
|
336
490
|
if (!pkgJson.files?.includes("vendor/pi-subagents")) {
|
|
337
|
-
fail(
|
|
338
|
-
'package.json "files" must include vendor/pi-subagents (npm publish ships subagents vendor)',
|
|
339
|
-
);
|
|
491
|
+
fail('package.json "files" must include vendor/pi-subagents (npm publish ships subagents vendor)');
|
|
340
492
|
}
|
|
341
493
|
ok('package.json files includes vendor/pi-subagents');
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
ROOT,
|
|
345
|
-
"vendor",
|
|
346
|
-
"pi-subagents",
|
|
347
|
-
"src",
|
|
348
|
-
"subagents.ts",
|
|
349
|
-
);
|
|
350
|
-
if (!(await fileExists(subagentsVendor))) {
|
|
351
|
-
fail("missing vendor/pi-subagents/src/subagents.ts");
|
|
352
|
-
}
|
|
494
|
+
const subagentsVendor = join(ROOT, "vendor", "pi-subagents", "src", "subagents.ts");
|
|
495
|
+
if (!(await fileExists(subagentsVendor))) fail("missing vendor/pi-subagents/src/subagents.ts");
|
|
353
496
|
const bridgePath = join(ROOT, ".pi", "lib", "harness-subagents-bridge.ts");
|
|
354
|
-
if (!(await fileExists(bridgePath)))
|
|
355
|
-
fail("missing harness-subagents-bridge.ts");
|
|
356
|
-
}
|
|
497
|
+
if (!(await fileExists(bridgePath))) fail("missing harness-subagents-bridge.ts");
|
|
357
498
|
const bridgeSrc = await readFile(bridgePath, "utf-8");
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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}`);
|
|
363
505
|
}
|
|
364
|
-
if (
|
|
365
|
-
|
|
366
|
-
!bridgeSrc.includes("subagentGovernanceExtensionPath")
|
|
367
|
-
) {
|
|
368
|
-
fail(
|
|
369
|
-
"harness-subagents-bridge must set subprocessGovernanceExtensionPath for all subagents",
|
|
370
|
-
);
|
|
506
|
+
if (!bridgeSrc.includes("subprocessGovernanceExtensionPath") && !bridgeSrc.includes("subagentGovernanceExtensionPath")) {
|
|
507
|
+
fail("harness-subagents-bridge must set subprocessGovernanceExtensionPath for all subagents");
|
|
371
508
|
}
|
|
372
509
|
const subagentsSrc = await readFile(subagentsVendor, "utf-8");
|
|
373
|
-
if (!subagentsSrc.includes("discoverAgents"))
|
|
374
|
-
|
|
375
|
-
}
|
|
376
|
-
if (!subagentsSrc.includes("packageRoot")) {
|
|
377
|
-
fail("vendor subagents.ts must pass packageRoot into discovery");
|
|
378
|
-
}
|
|
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");
|
|
379
512
|
ok("vendor pi-subagents + harness bridge");
|
|
380
513
|
|
|
381
|
-
const policyGateSrc = await readFile(
|
|
382
|
-
join(ROOT, ".pi", "extensions", "policy-gate.ts"),
|
|
383
|
-
"utf-8",
|
|
384
|
-
);
|
|
514
|
+
const policyGateSrc = await readFile(join(ROOT, ".pi", "extensions", "policy-gate.ts"), "utf-8");
|
|
385
515
|
if (!policyGateSrc.includes("isPlanPhaseAllowedMutation")) {
|
|
386
|
-
fail(
|
|
387
|
-
"policy-gate.ts must use isPlanPhaseAllowedMutation (plan-phase scoped writes)",
|
|
388
|
-
);
|
|
516
|
+
fail("policy-gate.ts must use isPlanPhaseAllowedMutation (plan-phase scoped writes)");
|
|
389
517
|
}
|
|
390
518
|
if (!policyGateSrc.includes('pi.on("tool_call", async (event, ctx)')) {
|
|
391
519
|
fail("policy-gate tool_call must receive ctx for run context");
|
|
@@ -394,108 +522,143 @@ async function main() {
|
|
|
394
522
|
fail("policy-gate.ts must delegate tool_call to AGT evaluateAgtHarnessToolCall");
|
|
395
523
|
}
|
|
396
524
|
const govPath = join(ROOT, ".pi", "extensions", "subagent-governance.ts");
|
|
397
|
-
const govAlias = join(
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
"extensions",
|
|
401
|
-
"harness-subagent-governance.ts",
|
|
402
|
-
);
|
|
403
|
-
if (!(await fileExists(govPath))) {
|
|
404
|
-
fail("missing subagent-governance.ts subprocess bundle");
|
|
405
|
-
}
|
|
406
|
-
if (!(await fileExists(govAlias))) {
|
|
407
|
-
fail("missing harness-subagent-governance.ts re-export alias");
|
|
408
|
-
}
|
|
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");
|
|
409
528
|
ok("policy-gate + subprocess governance");
|
|
529
|
+
}
|
|
410
530
|
|
|
531
|
+
async function runAgtPolicyDoctor() {
|
|
411
532
|
const agtDoctorPath = join(ROOT, ".pi", "scripts", "harness-agt-doctor.ts");
|
|
412
|
-
const { code: agtDoctorCode, out: agtDoctorOut } = await new Promise(
|
|
413
|
-
(
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
);
|
|
429
|
-
if (agtDoctorCode !== 0) {
|
|
430
|
-
fail(agtDoctorOut.trim() || "AGT policy doctor failed");
|
|
431
|
-
}
|
|
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");
|
|
432
549
|
ok("AGT policy doctor");
|
|
550
|
+
}
|
|
433
551
|
|
|
552
|
+
async function verifySmokeFixtures() {
|
|
434
553
|
const runCtxFixture = join(SMOKE, "run-context.fixture.json");
|
|
435
|
-
if (!(await fileExists(runCtxFixture)))
|
|
436
|
-
fail("missing run-context.fixture.json");
|
|
437
|
-
}
|
|
554
|
+
if (!(await fileExists(runCtxFixture))) fail("missing run-context.fixture.json");
|
|
438
555
|
const runCtxData = JSON.parse(await readFile(runCtxFixture, "utf-8"));
|
|
439
|
-
if (runCtxData.schema_version !== "1.0.0")
|
|
440
|
-
fail("run-context fixture schema_version must be 1.0.0");
|
|
441
|
-
}
|
|
556
|
+
if (runCtxData.schema_version !== "1.0.0") fail("run-context fixture schema_version must be 1.0.0");
|
|
442
557
|
if (!runCtxData.run_id) fail("run-context fixture missing run_id");
|
|
443
558
|
ok("run-context.fixture.json");
|
|
444
559
|
|
|
445
|
-
const fixture = JSON.parse(
|
|
446
|
-
await readFile(join(SMOKE, "run-record.fixture.json"), "utf-8"),
|
|
447
|
-
);
|
|
560
|
+
const fixture = JSON.parse(await readFile(join(SMOKE, "run-record.fixture.json"), "utf-8"));
|
|
448
561
|
validateRunRecordFixture(fixture);
|
|
449
562
|
ok("run-record.fixture.json");
|
|
450
563
|
|
|
451
|
-
const golden = JSON.parse(
|
|
452
|
-
await readFile(join(SMOKE, "test-diff-golden.json"), "utf-8"),
|
|
453
|
-
);
|
|
564
|
+
const golden = JSON.parse(await readFile(join(SMOKE, "test-diff-golden.json"), "utf-8"));
|
|
454
565
|
validateTestDiffGolden(golden);
|
|
455
566
|
ok("test-diff-golden.json");
|
|
567
|
+
}
|
|
456
568
|
|
|
457
|
-
|
|
458
|
-
|
|
569
|
+
async function verifyAgentsPolicyAndManifest() {
|
|
459
570
|
const AGENTS_POLICY = join(ROOT, ".pi", "harness", "agents.policy.yaml");
|
|
460
|
-
if (!(await fileExists(AGENTS_POLICY)))
|
|
461
|
-
fail("missing .pi/harness/agents.policy.yaml");
|
|
462
|
-
}
|
|
571
|
+
if (!(await fileExists(AGENTS_POLICY))) fail("missing .pi/harness/agents.policy.yaml");
|
|
463
572
|
ok("agents.policy.yaml present");
|
|
464
|
-
|
|
465
573
|
const policyYaml = await readFile(AGENTS_POLICY, "utf8");
|
|
466
574
|
if (!/^\s+extension_bundle:\s+executor/m.test(policyYaml)) {
|
|
467
575
|
fail("agents.policy.yaml kinds.executor must set extension_bundle: executor");
|
|
468
576
|
}
|
|
469
|
-
if (
|
|
470
|
-
/
|
|
471
|
-
) {
|
|
472
|
-
fail(
|
|
473
|
-
"harness/running/executor must not set extensions: true (use kind extension_bundle)",
|
|
474
|
-
);
|
|
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)");
|
|
475
579
|
}
|
|
476
580
|
ok("executor extension_bundle policy");
|
|
477
581
|
|
|
478
582
|
if (!(await fileExists(AGENTS_MANIFEST))) {
|
|
479
|
-
fail(
|
|
480
|
-
"missing .pi/harness/agents.manifest.json — run node \"$UP_PKG/.pi/scripts/harness-agents-manifest.mjs\" --write",
|
|
481
|
-
);
|
|
583
|
+
fail('missing .pi/harness/agents.manifest.json — run node "$UP_PKG/.pi/scripts/harness-agents-manifest.mjs" --write');
|
|
482
584
|
}
|
|
483
585
|
ok("agents.manifest.json present");
|
|
484
|
-
|
|
485
586
|
const { code: manifestCode, out: manifestOut } = await runNodeScript(
|
|
486
587
|
join(ROOT, ".pi", "scripts", "harness-agents-manifest.mjs"),
|
|
487
588
|
["--check"],
|
|
488
589
|
);
|
|
489
|
-
if (manifestCode !== 0)
|
|
490
|
-
fail(manifestOut.trim() || "agents.manifest.json drift — regenerate with --write");
|
|
491
|
-
}
|
|
590
|
+
if (manifestCode !== 0) fail(manifestOut.trim() || "agents.manifest.json drift — regenerate with --write");
|
|
492
591
|
ok("agents.manifest.json in sync");
|
|
592
|
+
}
|
|
493
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();
|
|
494
609
|
await checkWrsContracts();
|
|
495
|
-
|
|
496
610
|
console.log("\nharness:verify PASS");
|
|
497
611
|
}
|
|
498
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
|
+
|
|
499
662
|
async function checkWrsContracts() {
|
|
500
663
|
const systemMd = join(ROOT, ".pi", "SYSTEM.md");
|
|
501
664
|
const toolsTs = join(ROOT, ".pi", "extensions", "harness-web-tools.ts");
|