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.
Files changed (147) hide show
  1. package/.agents/skills/harness-decisions/SKILL.md +68 -2
  2. package/.agents/skills/harness-git-commit/SKILL.md +72 -0
  3. package/.agents/skills/harness-governor/SKILL.md +2 -2
  4. package/.agents/skills/harness-ls-lint-setup/SKILL.md +59 -0
  5. package/.agents/skills/harness-plan/SKILL.md +13 -11
  6. package/.agents/skills/harness-review/SKILL.md +1 -1
  7. package/.agents/skills/harness-sentrux-repair/SKILL.md +48 -0
  8. package/.agents/skills/sentrux/SKILL.md +4 -2
  9. package/.agents/skills/wiki-save/SKILL.md +1 -1
  10. package/.pi/PACKAGING.md +6 -0
  11. package/.pi/SYSTEM.md +21 -3
  12. package/.pi/agents/harness/ls-lint-steward.md +49 -0
  13. package/.pi/agents/harness/planning/decompose.md +4 -4
  14. package/.pi/agents/harness/reviewing/evaluator.md +1 -1
  15. package/.pi/agents/harness/running/executor.md +43 -2
  16. package/.pi/agents/harness/sentrux-repair-advisor.md +50 -0
  17. package/.pi/agents/pi-pi/prompt-expert.md +17 -2
  18. package/.pi/auto-commit.json +9 -2
  19. package/.pi/extensions/debate-orchestrator.ts +3 -0
  20. package/.pi/extensions/harness-anchored-edit.ts +139 -0
  21. package/.pi/extensions/harness-ask-user.ts +13 -34
  22. package/.pi/extensions/harness-debate-tools.ts +43 -4
  23. package/.pi/extensions/harness-live-widget.ts +28 -19
  24. package/.pi/extensions/harness-run-context.ts +278 -115
  25. package/.pi/extensions/harness-web-tools.ts +598 -471
  26. package/.pi/extensions/ls-lint-rules-sync.ts +103 -0
  27. package/.pi/extensions/observation-bus.ts +4 -0
  28. package/.pi/extensions/policy-gate.ts +270 -229
  29. package/.pi/extensions/sentrux-rules-sync.ts +2 -0
  30. package/.pi/extensions/soundboard.ts +48 -48
  31. package/.pi/harness/README.md +4 -0
  32. package/.pi/harness/agents.manifest.json +15 -7
  33. package/.pi/harness/agents.policy.yaml +47 -81
  34. package/.pi/harness/docs/adrs/0051-hash-anchored-executor-edits.md +41 -0
  35. package/.pi/harness/docs/adrs/0052-ls-lint-naming-lifecycle.md +45 -0
  36. package/.pi/harness/docs/adrs/0052-sentrux-structured-repair.md +38 -0
  37. package/.pi/harness/docs/adrs/0053-plan-task-clarification-gate.md +39 -0
  38. package/.pi/harness/docs/adrs/0054-harness-native-ask-user.md +40 -0
  39. package/.pi/harness/docs/adrs/0055-auto-commit-coauthor-lifecycle.md +40 -0
  40. package/.pi/harness/docs/adrs/README.md +7 -0
  41. package/.pi/harness/docs/practice-map.md +21 -5
  42. package/.pi/harness/evals/smoke/ls-lint-stub.json +10 -0
  43. package/.pi/harness/evolution/self-healing-rules.json +16 -0
  44. package/.pi/harness/ls-lint/naming.manifest.json +128 -0
  45. package/.pi/harness/sentrux/architecture.manifest.json +1 -1
  46. package/.pi/harness/specs/auto-commit.schema.json +63 -0
  47. package/.pi/harness/specs/ls-lint-manifest-proposal.schema.json +80 -0
  48. package/.pi/harness/specs/ls-lint-signal.schema.json +47 -0
  49. package/.pi/harness/specs/naming-manifest.schema.json +54 -0
  50. package/.pi/harness/specs/plan-task-clarification.schema.json +88 -0
  51. package/.pi/harness/specs/sentrux-diagnostics.schema.json +173 -0
  52. package/.pi/harness/specs/sentrux-repair-plan.schema.json +133 -0
  53. package/.pi/harness/specs/sentrux-report.schema.json +119 -0
  54. package/.pi/harness/specs/sentrux-signal.schema.json +34 -1
  55. package/.pi/lib/agents-policy.d.mts +26 -47
  56. package/.pi/lib/agents-policy.mjs +84 -29
  57. package/.pi/lib/agents-policy.ts +1 -0
  58. package/.pi/lib/agt/build-evaluation-context.ts +136 -64
  59. package/.pi/lib/ask-user/constants.mjs +3 -0
  60. package/.pi/lib/ask-user/constants.ts +4 -0
  61. package/.pi/lib/ask-user/contracts/glimpse-parse.ts +56 -0
  62. package/.pi/lib/ask-user/contracts/glimpse-payload-build.ts +58 -0
  63. package/.pi/lib/ask-user/contracts/glimpse-payload.ts +38 -0
  64. package/.pi/lib/ask-user/core/questionnaire.ts +74 -0
  65. package/.pi/lib/ask-user/dialog.ts +2 -314
  66. package/.pi/lib/ask-user/fallback.ts +2 -78
  67. package/.pi/lib/ask-user/format.ts +85 -0
  68. package/.pi/lib/ask-user/glimpseui.d.ts +10 -0
  69. package/.pi/lib/ask-user/index.ts +114 -0
  70. package/.pi/lib/ask-user/merge-task-clarification.ts +98 -0
  71. package/.pi/lib/ask-user/policy.mjs +43 -0
  72. package/.pi/lib/ask-user/policy.ts +104 -0
  73. package/.pi/lib/ask-user/presenters/glimpse.ts +130 -0
  74. package/.pi/lib/ask-user/presenters/headless.ts +131 -0
  75. package/.pi/lib/ask-user/presenters/select.ts +60 -0
  76. package/.pi/lib/ask-user/presenters/tui.ts +373 -0
  77. package/.pi/lib/ask-user/presenters/types.ts +13 -0
  78. package/.pi/lib/ask-user/render.ts +40 -9
  79. package/.pi/lib/ask-user/schema.ts +66 -13
  80. package/.pi/lib/ask-user/types.ts +60 -3
  81. package/.pi/lib/ask-user/validate-core.mjs +193 -7
  82. package/.pi/lib/ask-user/validate.ts +53 -34
  83. package/.pi/lib/harness-anchored-edit/.hash_anchors +1721 -0
  84. package/.pi/lib/harness-anchored-edit/anchor-state.ts +320 -0
  85. package/.pi/lib/harness-anchored-edit/apply-anchored-edits.ts +161 -0
  86. package/.pi/lib/harness-anchored-edit/edit-executor.ts +146 -0
  87. package/.pi/lib/harness-anchored-edit/index.ts +9 -0
  88. package/.pi/lib/harness-anchored-edit/line-protocol.ts +38 -0
  89. package/.pi/lib/harness-anchored-edit/package.json +3 -0
  90. package/.pi/lib/harness-anchored-edit/settings.ts +1 -0
  91. package/.pi/lib/harness-anchored-edit/task-id.ts +8 -0
  92. package/.pi/lib/harness-anchored-edit/types.ts +19 -0
  93. package/.pi/lib/harness-artifact-gate.ts +75 -21
  94. package/.pi/lib/harness-auto-commit-config.mjs +321 -0
  95. package/.pi/lib/harness-lens/clients/anchored-edit-autopatch.ts +158 -0
  96. package/.pi/lib/harness-lens/clients/lsp/client.ts +62 -39
  97. package/.pi/lib/harness-lens/clients/tool-policy.ts +73 -181
  98. package/.pi/lib/harness-lens/index.ts +246 -96
  99. package/.pi/lib/harness-lens/tools/lsp-navigation.ts +10 -8
  100. package/.pi/lib/harness-repair-brief.ts +84 -25
  101. package/.pi/lib/harness-run-context.ts +42 -52
  102. package/.pi/lib/harness-sentrux-parse.mjs +272 -0
  103. package/.pi/lib/harness-sentrux-root.mjs +78 -0
  104. package/.pi/lib/harness-slash-completions.ts +116 -0
  105. package/.pi/lib/harness-spawn-topology.ts +121 -87
  106. package/.pi/lib/harness-subagent-submit-registry.ts +10 -0
  107. package/.pi/lib/harness-subagents-bridge.ts +11 -6
  108. package/.pi/lib/harness-ui-state.ts +95 -48
  109. package/.pi/lib/plan-approval/dialog.ts +5 -0
  110. package/.pi/lib/plan-approval/validate.ts +1 -1
  111. package/.pi/lib/plan-approval-readiness.ts +32 -0
  112. package/.pi/lib/plan-debate-gate.ts +154 -114
  113. package/.pi/lib/plan-task-clarification.ts +158 -0
  114. package/.pi/prompts/harness-auto.md +2 -2
  115. package/.pi/prompts/harness-ls-lint-steward.md +43 -0
  116. package/.pi/prompts/harness-plan.md +58 -8
  117. package/.pi/prompts/harness-review.md +40 -6
  118. package/.pi/prompts/harness-run.md +33 -11
  119. package/.pi/prompts/harness-setup.md +72 -3
  120. package/.pi/prompts/harness-steer.md +3 -2
  121. package/.pi/prompts/wiki-save.md +5 -4
  122. package/.pi/scripts/README.md +8 -0
  123. package/.pi/scripts/generate-agents-policy-yaml.mjs +14 -2
  124. package/.pi/scripts/harness-anchored-edit-smoke.mjs +45 -0
  125. package/.pi/scripts/harness-auto-commit-bootstrap.mjs +96 -0
  126. package/.pi/scripts/harness-cli-verify.sh +47 -0
  127. package/.pi/scripts/harness-git-churn.mjs +77 -0
  128. package/.pi/scripts/harness-git-commit.mjs +173 -0
  129. package/.pi/scripts/harness-ls-lint-bootstrap.mjs +142 -0
  130. package/.pi/scripts/harness-ls-lint-cli.mjs +184 -0
  131. package/.pi/scripts/harness-seed-project-contracts.mjs +47 -0
  132. package/.pi/scripts/harness-sentrux-diagnostics.mjs +230 -0
  133. package/.pi/scripts/harness-sentrux-report.mjs +256 -0
  134. package/.pi/scripts/harness-verify.mjs +347 -117
  135. package/.pi/scripts/ls-lint-rules-sync.mjs +265 -0
  136. package/.pi/scripts/run-tests.mjs +65 -0
  137. package/.pi/settings.example.json +1 -0
  138. package/.sentrux/rules.toml +1 -1
  139. package/AGENTS.md +1 -0
  140. package/CHANGELOG.md +31 -0
  141. package/README.md +13 -4
  142. package/THIRD_PARTY_NOTICES.md +7 -0
  143. package/package.json +8 -3
  144. package/vendor/pi-subagents/src/agents.ts +5 -0
  145. package/vendor/pi-subagents/src/subagents.ts +22 -3
  146. package/vendor/pi-vcc/src/hooks/before-compact.ts +86 -60
  147. 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 main() {
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
- const libPath = join(ROOT, ".pi", "lib", "harness-posthog.ts");
270
- if (!(await fileExists(libPath))) fail("missing lib/harness-posthog.ts");
271
- ok("lib/harness-posthog.ts");
272
-
273
- const runCtxLib = join(ROOT, ".pi", "lib", "harness-run-context.ts");
274
- if (!(await fileExists(runCtxLib))) fail("missing lib/harness-run-context.ts");
275
- ok("lib/harness-run-context.ts");
276
-
277
- const pkgJson = JSON.parse(
278
- await readFile(join(ROOT, "package.json"), "utf-8"),
279
- );
280
- await checkHarnessLens(pkgJson);
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
- const subagentsVendor = join(
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
- if (!bridgeSrc.includes("precheckHarnessSubagentSpawn")) {
305
- fail("harness-subagents-bridge must run precheckHarnessSubagentSpawn");
306
- }
307
- if (!bridgeSrc.includes("packageRoot")) {
308
- fail("harness-subagents-bridge must pass packageRoot for agent discovery");
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
- !bridgeSrc.includes("subprocessGovernanceExtensionPath") &&
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
- fail("vendor subagents.ts must implement discoverAgents");
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
- ROOT,
345
- ".pi",
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
- (resolve) => {
360
- const child = spawn(
361
- "npx",
362
- ["-y", "tsx", agtDoctorPath],
363
- { cwd: ROOT, stdio: ["ignore", "pipe", "pipe"], shell: true },
364
- );
365
- let out = "";
366
- child.stdout?.on("data", (d) => {
367
- out += d.toString();
368
- });
369
- child.stderr?.on("data", (d) => {
370
- out += d.toString();
371
- });
372
- child.on("close", (code) => resolve({ code: code ?? 1, out }));
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
- await checkSentruxGate();
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");