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.
Files changed (130) 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 +1 -1
  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 +7 -9
  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 +49 -82
  34. package/.pi/harness/docs/adrs/0052-ls-lint-naming-lifecycle.md +45 -0
  35. package/.pi/harness/docs/adrs/0052-sentrux-structured-repair.md +38 -0
  36. package/.pi/harness/docs/adrs/0053-plan-task-clarification-gate.md +39 -0
  37. package/.pi/harness/docs/adrs/0054-harness-native-ask-user.md +40 -0
  38. package/.pi/harness/docs/adrs/0055-auto-commit-coauthor-lifecycle.md +40 -0
  39. package/.pi/harness/docs/adrs/README.md +5 -0
  40. package/.pi/harness/docs/practice-map.md +10 -5
  41. package/.pi/harness/evals/smoke/ls-lint-stub.json +10 -0
  42. package/.pi/harness/evolution/self-healing-rules.json +16 -0
  43. package/.pi/harness/ls-lint/naming.manifest.json +128 -0
  44. package/.pi/harness/sentrux/architecture.manifest.json +1 -1
  45. package/.pi/harness/specs/auto-commit.schema.json +63 -0
  46. package/.pi/harness/specs/ls-lint-manifest-proposal.schema.json +80 -0
  47. package/.pi/harness/specs/ls-lint-signal.schema.json +47 -0
  48. package/.pi/harness/specs/naming-manifest.schema.json +54 -0
  49. package/.pi/harness/specs/plan-task-clarification.schema.json +88 -0
  50. package/.pi/harness/specs/sentrux-diagnostics.schema.json +173 -0
  51. package/.pi/harness/specs/sentrux-repair-plan.schema.json +133 -0
  52. package/.pi/harness/specs/sentrux-report.schema.json +119 -0
  53. package/.pi/harness/specs/sentrux-signal.schema.json +34 -1
  54. package/.pi/lib/agents-policy.d.mts +26 -51
  55. package/.pi/lib/agents-policy.mjs +41 -28
  56. package/.pi/lib/agt/build-evaluation-context.ts +136 -64
  57. package/.pi/lib/ask-user/constants.mjs +3 -0
  58. package/.pi/lib/ask-user/constants.ts +4 -0
  59. package/.pi/lib/ask-user/contracts/glimpse-parse.ts +56 -0
  60. package/.pi/lib/ask-user/contracts/glimpse-payload-build.ts +58 -0
  61. package/.pi/lib/ask-user/contracts/glimpse-payload.ts +38 -0
  62. package/.pi/lib/ask-user/core/questionnaire.ts +74 -0
  63. package/.pi/lib/ask-user/dialog.ts +2 -314
  64. package/.pi/lib/ask-user/fallback.ts +2 -78
  65. package/.pi/lib/ask-user/format.ts +85 -0
  66. package/.pi/lib/ask-user/glimpseui.d.ts +10 -0
  67. package/.pi/lib/ask-user/index.ts +114 -0
  68. package/.pi/lib/ask-user/merge-task-clarification.ts +98 -0
  69. package/.pi/lib/ask-user/policy.mjs +43 -0
  70. package/.pi/lib/ask-user/policy.ts +104 -0
  71. package/.pi/lib/ask-user/presenters/glimpse.ts +130 -0
  72. package/.pi/lib/ask-user/presenters/headless.ts +131 -0
  73. package/.pi/lib/ask-user/presenters/select.ts +60 -0
  74. package/.pi/lib/ask-user/presenters/tui.ts +373 -0
  75. package/.pi/lib/ask-user/presenters/types.ts +13 -0
  76. package/.pi/lib/ask-user/render.ts +40 -9
  77. package/.pi/lib/ask-user/schema.ts +66 -13
  78. package/.pi/lib/ask-user/types.ts +60 -3
  79. package/.pi/lib/ask-user/validate-core.mjs +193 -7
  80. package/.pi/lib/ask-user/validate.ts +53 -34
  81. package/.pi/lib/harness-anchored-edit/package.json +3 -0
  82. package/.pi/lib/harness-artifact-gate.ts +75 -21
  83. package/.pi/lib/harness-auto-commit-config.mjs +321 -0
  84. package/.pi/lib/harness-lens/clients/lsp/client.ts +62 -39
  85. package/.pi/lib/harness-lens/clients/tool-policy.ts +73 -181
  86. package/.pi/lib/harness-lens/index.ts +241 -108
  87. package/.pi/lib/harness-lens/tools/lsp-navigation.ts +10 -8
  88. package/.pi/lib/harness-repair-brief.ts +84 -25
  89. package/.pi/lib/harness-run-context.ts +42 -52
  90. package/.pi/lib/harness-sentrux-parse.mjs +272 -0
  91. package/.pi/lib/harness-sentrux-root.mjs +78 -0
  92. package/.pi/lib/harness-slash-completions.ts +116 -0
  93. package/.pi/lib/harness-spawn-topology.ts +121 -87
  94. package/.pi/lib/harness-subagent-submit-registry.ts +10 -0
  95. package/.pi/lib/harness-subagents-bridge.ts +4 -1
  96. package/.pi/lib/harness-ui-state.ts +95 -48
  97. package/.pi/lib/plan-approval/dialog.ts +5 -0
  98. package/.pi/lib/plan-approval/validate.ts +1 -1
  99. package/.pi/lib/plan-approval-readiness.ts +32 -0
  100. package/.pi/lib/plan-debate-gate.ts +154 -114
  101. package/.pi/lib/plan-task-clarification.ts +158 -0
  102. package/.pi/prompts/harness-auto.md +2 -2
  103. package/.pi/prompts/harness-ls-lint-steward.md +43 -0
  104. package/.pi/prompts/harness-plan.md +58 -8
  105. package/.pi/prompts/harness-review.md +40 -6
  106. package/.pi/prompts/harness-run.md +33 -11
  107. package/.pi/prompts/harness-setup.md +72 -3
  108. package/.pi/prompts/harness-steer.md +2 -1
  109. package/.pi/prompts/wiki-save.md +5 -4
  110. package/.pi/scripts/README.md +8 -0
  111. package/.pi/scripts/generate-agents-policy-yaml.mjs +14 -2
  112. package/.pi/scripts/harness-auto-commit-bootstrap.mjs +96 -0
  113. package/.pi/scripts/harness-cli-verify.sh +47 -0
  114. package/.pi/scripts/harness-git-churn.mjs +77 -0
  115. package/.pi/scripts/harness-git-commit.mjs +173 -0
  116. package/.pi/scripts/harness-ls-lint-bootstrap.mjs +142 -0
  117. package/.pi/scripts/harness-ls-lint-cli.mjs +184 -0
  118. package/.pi/scripts/harness-seed-project-contracts.mjs +47 -0
  119. package/.pi/scripts/harness-sentrux-diagnostics.mjs +230 -0
  120. package/.pi/scripts/harness-sentrux-report.mjs +256 -0
  121. package/.pi/scripts/harness-verify.mjs +288 -125
  122. package/.pi/scripts/ls-lint-rules-sync.mjs +265 -0
  123. package/.pi/scripts/run-tests.mjs +1 -0
  124. package/.pi/settings.example.json +1 -0
  125. package/.sentrux/rules.toml +1 -1
  126. package/AGENTS.md +1 -0
  127. package/CHANGELOG.md +25 -0
  128. package/README.md +13 -4
  129. package/package.json +5 -1
  130. 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 main() {
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
- const libPath = join(ROOT, ".pi", "lib", "harness-posthog.ts");
323
- if (!(await fileExists(libPath))) fail("missing lib/harness-posthog.ts");
324
- ok("lib/harness-posthog.ts");
325
-
326
- const runCtxLib = join(ROOT, ".pi", "lib", "harness-run-context.ts");
327
- if (!(await fileExists(runCtxLib))) fail("missing lib/harness-run-context.ts");
328
- ok("lib/harness-run-context.ts");
329
-
330
- const pkgJson = JSON.parse(
331
- await readFile(join(ROOT, "package.json"), "utf-8"),
332
- );
333
- await checkHarnessLens(pkgJson);
334
- await checkHarnessAnchoredEdit(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
+ }
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
- const subagentsVendor = join(
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
- if (!bridgeSrc.includes("precheckHarnessSubagentSpawn")) {
359
- fail("harness-subagents-bridge must run precheckHarnessSubagentSpawn");
360
- }
361
- if (!bridgeSrc.includes("packageRoot")) {
362
- 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}`);
363
505
  }
364
- if (
365
- !bridgeSrc.includes("subprocessGovernanceExtensionPath") &&
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
- fail("vendor subagents.ts must implement discoverAgents");
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
- ROOT,
399
- ".pi",
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
- (resolve) => {
414
- const child = spawn(
415
- "npx",
416
- ["-y", "tsx", agtDoctorPath],
417
- { cwd: ROOT, stdio: ["ignore", "pipe", "pipe"], shell: true },
418
- );
419
- let out = "";
420
- child.stdout?.on("data", (d) => {
421
- out += d.toString();
422
- });
423
- child.stderr?.on("data", (d) => {
424
- out += d.toString();
425
- });
426
- child.on("close", (code) => resolve({ code: code ?? 1, out }));
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
- await checkSentruxGate();
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
- /harness\/running\/executor:[\s\S]*?extensions:\s+true/m.test(policyYaml)
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");