ultimate-pi 0.20.0 → 0.22.1

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 (149) hide show
  1. package/.agents/skills/harness-context/SKILL.md +3 -3
  2. package/.agents/skills/harness-debate-plan/SKILL.md +2 -2
  3. package/.agents/skills/harness-decisions/SKILL.md +68 -2
  4. package/.agents/skills/harness-eval/SKILL.md +1 -1
  5. package/.agents/skills/harness-git-commit/SKILL.md +72 -0
  6. package/.agents/skills/harness-governor/SKILL.md +6 -6
  7. package/.agents/skills/harness-ls-lint-setup/SKILL.md +59 -0
  8. package/.agents/skills/harness-orchestration/SKILL.md +4 -4
  9. package/.agents/skills/harness-plan/SKILL.md +14 -12
  10. package/.agents/skills/harness-review/SKILL.md +3 -3
  11. package/.agents/skills/harness-sentrux-repair/SKILL.md +48 -0
  12. package/.agents/skills/harness-sentrux-setup/SKILL.md +2 -2
  13. package/.agents/skills/harness-spec/SKILL.md +1 -1
  14. package/.agents/skills/harness-steer/SKILL.md +2 -2
  15. package/.agents/skills/posthog-analyst/SKILL.md +1 -1
  16. package/.agents/skills/sentrux/SKILL.md +6 -4
  17. package/.agents/skills/web-retrieval/SKILL.md +1 -1
  18. package/.agents/skills/wiki-save/SKILL.md +1 -1
  19. package/.pi/PACKAGING.md +6 -0
  20. package/.pi/SYSTEM.md +21 -3
  21. package/.pi/agents/harness/ls-lint-steward.md +49 -0
  22. package/.pi/agents/harness/planning/decompose.md +5 -5
  23. package/.pi/agents/harness/planning/execution-plan-author.md +1 -1
  24. package/.pi/agents/harness/planning/hypothesis-validator.md +1 -1
  25. package/.pi/agents/harness/planning/hypothesis.md +1 -1
  26. package/.pi/agents/harness/planning/plan-adversary.md +1 -1
  27. package/.pi/agents/harness/planning/plan-evaluator.md +2 -2
  28. package/.pi/agents/harness/planning/plan-synthesizer.md +2 -2
  29. package/.pi/agents/harness/planning/review-integrator.md +1 -1
  30. package/.pi/agents/harness/planning/sprint-contract-auditor.md +5 -5
  31. package/.pi/agents/harness/reviewing/evaluator.md +1 -1
  32. package/.pi/agents/harness/running/executor.md +2 -2
  33. package/.pi/agents/harness/sentrux-repair-advisor.md +50 -0
  34. package/.pi/agents/harness/sentrux-steward.md +2 -2
  35. package/.pi/agents/pi-pi/prompt-expert.md +17 -2
  36. package/.pi/auto-commit.json +9 -2
  37. package/.pi/extensions/debate-orchestrator.ts +3 -0
  38. package/.pi/extensions/harness-anchored-edit.ts +7 -9
  39. package/.pi/extensions/harness-ask-user.ts +13 -34
  40. package/.pi/extensions/harness-debate-tools.ts +43 -4
  41. package/.pi/extensions/harness-live-widget.ts +28 -19
  42. package/.pi/extensions/harness-run-context.ts +278 -115
  43. package/.pi/extensions/harness-web-tools.ts +598 -471
  44. package/.pi/extensions/ls-lint-rules-sync.ts +103 -0
  45. package/.pi/extensions/observation-bus.ts +4 -0
  46. package/.pi/extensions/policy-gate.ts +270 -229
  47. package/.pi/extensions/sentrux-rules-sync.ts +2 -0
  48. package/.pi/extensions/soundboard.ts +48 -48
  49. package/.pi/harness/README.md +4 -0
  50. package/.pi/harness/agents.manifest.json +24 -16
  51. package/.pi/harness/agents.policy.yaml +49 -82
  52. package/.pi/harness/docs/adrs/0052-ls-lint-naming-lifecycle.md +45 -0
  53. package/.pi/harness/docs/adrs/0052-sentrux-structured-repair.md +38 -0
  54. package/.pi/harness/docs/adrs/0053-plan-task-clarification-gate.md +39 -0
  55. package/.pi/harness/docs/adrs/0054-harness-native-ask-user.md +40 -0
  56. package/.pi/harness/docs/adrs/0055-auto-commit-coauthor-lifecycle.md +40 -0
  57. package/.pi/harness/docs/adrs/README.md +5 -0
  58. package/.pi/harness/docs/practice-map.md +10 -5
  59. package/.pi/harness/evals/smoke/ls-lint-stub.json +10 -0
  60. package/.pi/harness/evolution/self-healing-rules.json +16 -0
  61. package/.pi/harness/ls-lint/naming.manifest.json +128 -0
  62. package/.pi/harness/sentrux/architecture.manifest.json +1 -1
  63. package/.pi/harness/specs/auto-commit.schema.json +63 -0
  64. package/.pi/harness/specs/ls-lint-manifest-proposal.schema.json +80 -0
  65. package/.pi/harness/specs/ls-lint-signal.schema.json +47 -0
  66. package/.pi/harness/specs/naming-manifest.schema.json +54 -0
  67. package/.pi/harness/specs/plan-task-clarification.schema.json +88 -0
  68. package/.pi/harness/specs/sentrux-diagnostics.schema.json +173 -0
  69. package/.pi/harness/specs/sentrux-repair-plan.schema.json +133 -0
  70. package/.pi/harness/specs/sentrux-report.schema.json +119 -0
  71. package/.pi/harness/specs/sentrux-signal.schema.json +34 -1
  72. package/.pi/lib/agents-policy.d.mts +26 -51
  73. package/.pi/lib/agents-policy.mjs +41 -28
  74. package/.pi/lib/agt/build-evaluation-context.ts +136 -64
  75. package/.pi/lib/ask-user/constants.mjs +3 -0
  76. package/.pi/lib/ask-user/constants.ts +4 -0
  77. package/.pi/lib/ask-user/contracts/glimpse-parse.ts +56 -0
  78. package/.pi/lib/ask-user/contracts/glimpse-payload-build.ts +58 -0
  79. package/.pi/lib/ask-user/contracts/glimpse-payload.ts +38 -0
  80. package/.pi/lib/ask-user/core/questionnaire.ts +74 -0
  81. package/.pi/lib/ask-user/dialog.ts +2 -314
  82. package/.pi/lib/ask-user/fallback.ts +2 -78
  83. package/.pi/lib/ask-user/format.ts +85 -0
  84. package/.pi/lib/ask-user/glimpseui.d.ts +10 -0
  85. package/.pi/lib/ask-user/index.ts +114 -0
  86. package/.pi/lib/ask-user/merge-task-clarification.ts +98 -0
  87. package/.pi/lib/ask-user/policy.mjs +43 -0
  88. package/.pi/lib/ask-user/policy.ts +104 -0
  89. package/.pi/lib/ask-user/presenters/glimpse.ts +130 -0
  90. package/.pi/lib/ask-user/presenters/headless.ts +131 -0
  91. package/.pi/lib/ask-user/presenters/select.ts +60 -0
  92. package/.pi/lib/ask-user/presenters/tui.ts +373 -0
  93. package/.pi/lib/ask-user/presenters/types.ts +13 -0
  94. package/.pi/lib/ask-user/render.ts +40 -9
  95. package/.pi/lib/ask-user/schema.ts +66 -13
  96. package/.pi/lib/ask-user/types.ts +60 -3
  97. package/.pi/lib/ask-user/validate-core.mjs +193 -7
  98. package/.pi/lib/ask-user/validate.ts +53 -34
  99. package/.pi/lib/harness-anchored-edit/package.json +3 -0
  100. package/.pi/lib/harness-artifact-gate.ts +75 -21
  101. package/.pi/lib/harness-auto-commit-config.mjs +321 -0
  102. package/.pi/lib/harness-lens/clients/lsp/client.ts +62 -39
  103. package/.pi/lib/harness-lens/clients/tool-policy.ts +73 -181
  104. package/.pi/lib/harness-lens/index.ts +241 -108
  105. package/.pi/lib/harness-lens/tools/lsp-navigation.ts +10 -8
  106. package/.pi/lib/harness-repair-brief.ts +84 -25
  107. package/.pi/lib/harness-run-context.ts +42 -52
  108. package/.pi/lib/harness-sentrux-parse.mjs +272 -0
  109. package/.pi/lib/harness-sentrux-root.mjs +78 -0
  110. package/.pi/lib/harness-slash-completions.ts +116 -0
  111. package/.pi/lib/harness-spawn-topology.ts +121 -87
  112. package/.pi/lib/harness-subagent-submit-registry.ts +10 -0
  113. package/.pi/lib/harness-subagents-bridge.ts +4 -1
  114. package/.pi/lib/harness-ui-state.ts +95 -48
  115. package/.pi/lib/plan-approval/dialog.ts +5 -0
  116. package/.pi/lib/plan-approval/validate.ts +1 -1
  117. package/.pi/lib/plan-approval-readiness.ts +32 -0
  118. package/.pi/lib/plan-debate-gate.ts +154 -114
  119. package/.pi/lib/plan-task-clarification.ts +158 -0
  120. package/.pi/prompts/harness-auto.md +2 -2
  121. package/.pi/prompts/harness-ls-lint-steward.md +43 -0
  122. package/.pi/prompts/harness-plan.md +63 -13
  123. package/.pi/prompts/harness-review.md +44 -10
  124. package/.pi/prompts/harness-run.md +35 -13
  125. package/.pi/prompts/harness-sentrux-steward.md +2 -2
  126. package/.pi/prompts/harness-setup.md +74 -5
  127. package/.pi/prompts/harness-steer.md +6 -5
  128. package/.pi/prompts/wiki-save.md +5 -4
  129. package/.pi/scripts/README.md +8 -0
  130. package/.pi/scripts/generate-agents-policy-yaml.mjs +14 -2
  131. package/.pi/scripts/harness-auto-commit-bootstrap.mjs +96 -0
  132. package/.pi/scripts/harness-cli-verify.sh +47 -0
  133. package/.pi/scripts/harness-git-churn.mjs +77 -0
  134. package/.pi/scripts/harness-git-commit.mjs +173 -0
  135. package/.pi/scripts/harness-ls-lint-bootstrap.mjs +142 -0
  136. package/.pi/scripts/harness-ls-lint-cli.mjs +184 -0
  137. package/.pi/scripts/harness-seed-project-contracts.mjs +47 -0
  138. package/.pi/scripts/harness-sentrux-diagnostics.mjs +230 -0
  139. package/.pi/scripts/harness-sentrux-report.mjs +256 -0
  140. package/.pi/scripts/harness-verify.mjs +361 -125
  141. package/.pi/scripts/ls-lint-rules-sync.mjs +265 -0
  142. package/.pi/scripts/run-tests.mjs +1 -0
  143. package/.pi/settings.example.json +1 -0
  144. package/.sentrux/rules.toml +1 -1
  145. package/AGENTS.md +2 -0
  146. package/CHANGELOG.md +32 -0
  147. package/README.md +13 -4
  148. package/package.json +13 -6
  149. 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,132 @@ async function runNodeScript(scriptPath, args = []) {
129
157
  });
130
158
  }
131
159
 
160
+ const PROMPT_EXCLUDE = new Set(["release.md"]);
161
+ const INTERNAL_PROMPT_SURFACE_ROOTS = [
162
+ {
163
+ label: ".pi/prompts",
164
+ dir: join(ROOT, ".pi", "prompts"),
165
+ recursive: false,
166
+ include: (name) => name.endsWith(".md"),
167
+ },
168
+ {
169
+ label: ".pi/agents",
170
+ dir: join(ROOT, ".pi", "agents"),
171
+ recursive: true,
172
+ include: (name) => name.endsWith(".md"),
173
+ },
174
+ {
175
+ label: ".agents/skills",
176
+ dir: join(ROOT, ".agents", "skills"),
177
+ recursive: true,
178
+ include: (name) => name === "SKILL.md",
179
+ },
180
+ ];
181
+
182
+ const FORBIDDEN_INTERNAL_PROMPT_REFS = [
183
+ { label: "ADR token", regex: /\bADR\b/i },
184
+ { label: "internal ADR path", regex: /(?:^|\W)(?:docs\/adr|\.pi\/harness\/docs\/adrs)(?:\W|$)/i },
185
+ { label: "internal practice-map path", regex: /(?:^|\W)(?:\.pi\/harness\/docs\/practice-map\.md|practice-map)(?:\W|$)/i },
186
+ { label: "internal planning rubrics path", regex: /(?:^|\W)(?:\.pi\/harness\/docs\/planning-rubrics\.md|planning-rubrics)(?:\W|$)/i },
187
+ { label: "internal docs path", regex: /(?:^|\W)\.pi\/harness\/docs\//i },
188
+ ];
189
+
190
+ function parsePromptFrontmatter(raw) {
191
+ const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
192
+ if (!match) return null;
193
+ const fields = {};
194
+ for (const line of match[1].split("\n")) {
195
+ const idx = line.indexOf(":");
196
+ if (idx === -1) continue;
197
+ const key = line.slice(0, idx).trim();
198
+ let value = line.slice(idx + 1).trim();
199
+ if (
200
+ (value.startsWith('"') && value.endsWith('"')) ||
201
+ (value.startsWith("'") && value.endsWith("'"))
202
+ ) {
203
+ value = value.slice(1, -1);
204
+ }
205
+ fields[key] = value;
206
+ }
207
+ return fields;
208
+ }
209
+
210
+ function relPath(path) {
211
+ if (path.startsWith(`${ROOT}/`)) return path.slice(ROOT.length + 1);
212
+ return path;
213
+ }
214
+
215
+ async function collectMarkdownFiles(dir, { recursive, include }) {
216
+ const out = [];
217
+ const entries = await readdir(dir, { withFileTypes: true });
218
+ for (const entry of entries) {
219
+ const fullPath = join(dir, entry.name);
220
+ if (entry.isDirectory()) {
221
+ if (recursive) {
222
+ out.push(...(await collectMarkdownFiles(fullPath, { recursive, include })));
223
+ }
224
+ continue;
225
+ }
226
+ if (!entry.isFile()) continue;
227
+ if (!entry.name.endsWith(".md")) continue;
228
+ if (include && !include(entry.name, fullPath)) continue;
229
+ out.push(fullPath);
230
+ }
231
+ return out;
232
+ }
233
+
234
+ async function checkInternalPromptReferencePolicy() {
235
+ for (const root of INTERNAL_PROMPT_SURFACE_ROOTS) {
236
+ if (!(await fileExists(root.dir))) continue;
237
+ const files = await collectMarkdownFiles(root.dir, {
238
+ recursive: root.recursive,
239
+ include: root.include,
240
+ });
241
+ for (const file of files) {
242
+ const raw = await readFile(file, "utf-8");
243
+ for (const rule of FORBIDDEN_INTERNAL_PROMPT_REFS) {
244
+ if (rule.regex.test(raw)) {
245
+ fail(
246
+ `internal prompt/agent/skill policy: ${relPath(file)} contains forbidden reference (${rule.label})`,
247
+ );
248
+ }
249
+ }
250
+ }
251
+ ok(`internal prompt-surface reference policy (${root.label})`);
252
+ }
253
+ }
254
+ async function checkPromptFrontmatter() {
255
+ const promptsDir = join(ROOT, ".pi", "prompts");
256
+ const names = await readdir(promptsDir);
257
+ for (const name of names) {
258
+ if (!name.endsWith(".md") || PROMPT_EXCLUDE.has(name)) continue;
259
+ const path = join(promptsDir, name);
260
+ const raw = await readFile(path, "utf-8");
261
+ const fm = parsePromptFrontmatter(raw);
262
+ if (!fm) fail(`prompt ${name}: missing YAML frontmatter`);
263
+ const description = fm.description?.trim();
264
+ if (!description) fail(`prompt ${name}: missing or empty description`);
265
+ if (Object.hasOwn(fm, "argument-hint") && fm["argument-hint"] === "") {
266
+ fail(`prompt ${name}: argument-hint must be omitted or non-empty (not "")`);
267
+ }
268
+ const body = raw.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "");
269
+ const usesArgs = /\$ARGUMENTS|\$1|\$2|\$@/.test(body);
270
+ const stepZeroFlags =
271
+ /Step 0 — Parse arguments/.test(body) &&
272
+ /\[--[^\]]+\]/.test(body) &&
273
+ name !== "harness-run.md";
274
+ if (
275
+ (usesArgs || stepZeroFlags) &&
276
+ !fm["argument-hint"]?.trim()
277
+ ) {
278
+ fail(
279
+ `prompt ${name}: requires argument-hint (uses $ARGUMENTS/$1 or Step 0 flags)`,
280
+ );
281
+ }
282
+ ok(`prompt frontmatter ${name}`);
283
+ }
284
+ }
285
+
132
286
  async function checkSentruxRules() {
133
287
  if (!(await fileExists(SENTRUX_MANIFEST))) {
134
288
  fail("missing .pi/harness/sentrux/architecture.manifest.json");
@@ -260,6 +414,74 @@ async function checkHarnessAnchoredEdit(pkgJson) {
260
414
  ok("harness-anchored-edit first-class contract");
261
415
  }
262
416
 
417
+ async function checkLsLintRules() {
418
+ if (!(await fileExists(LS_LINT_MANIFEST))) {
419
+ fail("missing .pi/harness/ls-lint/naming.manifest.json");
420
+ }
421
+ ok("ls-lint naming.manifest.json");
422
+
423
+ const syncScript = join(ROOT, ".pi", "scripts", "ls-lint-rules-sync.mjs");
424
+ const { code: checkCode, out: checkOut } = await runNodeScript(syncScript, [
425
+ "--check",
426
+ ]);
427
+ if (checkCode !== 0) {
428
+ fail(
429
+ checkOut.trim() ||
430
+ '.ls-lint.yml out of date — run node "$UP_PKG/.pi/scripts/ls-lint-rules-sync.mjs" --force',
431
+ );
432
+ }
433
+ ok(".ls-lint.yml in sync with manifest");
434
+
435
+ if (!(await fileExists(LS_LINT_YML))) {
436
+ fail(
437
+ 'missing .ls-lint.yml — run node "$UP_PKG/.pi/scripts/ls-lint-rules-sync.mjs" --force',
438
+ );
439
+ }
440
+ ok(".ls-lint.yml present");
441
+ }
442
+
443
+ async function checkLsLintGate() {
444
+ await checkLsLintRules();
445
+
446
+ if (process.env.HARNESS_LS_LINT_REQUIRED !== "true") {
447
+ ok("ls-lint signal gate skipped (HARNESS_LS_LINT_REQUIRED not set)");
448
+ return;
449
+ }
450
+ const runDir = process.env.HARNESS_RUN_DIR?.trim();
451
+ const runSignalPath = runDir
452
+ ? join(runDir, "artifacts", "ls-lint-signal.yaml")
453
+ : null;
454
+ if (runSignalPath && (await fileExists(runSignalPath))) {
455
+ ok(`ls-lint run signal present (${runSignalPath})`);
456
+ } else {
457
+ const stubPath = join(
458
+ ROOT,
459
+ ".pi",
460
+ "harness",
461
+ "evals",
462
+ "smoke",
463
+ "ls-lint-stub.json",
464
+ );
465
+ if (!(await fileExists(stubPath))) {
466
+ fail(
467
+ "HARNESS_LS_LINT_REQUIRED=true but no artifacts/ls-lint-signal.yaml and .pi/harness/evals/smoke/ls-lint-stub.json missing",
468
+ );
469
+ }
470
+ ok("ls-lint stub present (run signal absent — prefer artifacts/ls-lint-signal.yaml from /harness-run)");
471
+ }
472
+
473
+ const cliScript = join(ROOT, ".pi", "scripts", "harness-ls-lint-cli.mjs");
474
+ const { code, out } = await runNodeScript(cliScript, []);
475
+ if (code === 127 || (out && out.includes("not installed"))) {
476
+ ok("ls-lint CLI check skipped (not installed)");
477
+ return;
478
+ }
479
+ if (code !== 0) {
480
+ fail(out.trim() || "ls-lint check failed — fix path violations or update manifest");
481
+ }
482
+ ok("ls-lint check passed");
483
+ }
484
+
263
485
  async function checkSentruxGate() {
264
486
  await checkSentruxRules();
265
487
 
@@ -297,95 +519,73 @@ async function checkSentruxGate() {
297
519
  ok("sentrux check passed");
298
520
  }
299
521
 
300
- async function main() {
301
- console.log("harness:verify\n");
302
-
522
+ async function verifySchemaAdrAndExtensions() {
303
523
  for (const name of REQUIRED_SCHEMAS) {
304
524
  const path = join(SPECS, name);
305
525
  if (!(await fileExists(path))) fail(`missing schema ${name}`);
306
526
  JSON.parse(await readFile(path, "utf-8"));
307
527
  ok(`schema ${name}`);
308
528
  }
309
-
310
529
  for (const name of REQUIRED_ADRS) {
311
530
  const path = join(ADRS, name);
312
531
  if (!(await fileExists(path))) fail(`missing ADR ${name}`);
313
532
  ok(`ADR ${name}`);
314
533
  }
315
-
316
534
  for (const name of REQUIRED_EXTENSIONS) {
317
535
  const path = join(ROOT, ".pi", "extensions", name);
318
536
  if (!(await fileExists(path))) fail(`missing extension ${name}`);
319
537
  ok(`extension ${name}`);
320
538
  }
539
+ }
321
540
 
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);
541
+ async function verifyCoreSurfaceFiles() {
542
+ const required = [
543
+ { path: join(ROOT, ".pi", "lib", "harness-posthog.ts"), msg: "lib/harness-posthog.ts" },
544
+ { path: join(ROOT, ".pi", "lib", "harness-run-context.ts"), msg: "lib/harness-run-context.ts" },
545
+ { path: join(ROOT, ".pi", "extensions", "harness-ask-user.ts"), msg: "extension harness-ask-user.ts" },
546
+ { path: join(ROOT, ".pi", "lib", "harness-slash-completions.ts"), msg: "lib/harness-slash-completions.ts" },
547
+ ];
548
+ for (const item of required) {
549
+ if (!(await fileExists(item.path))) fail(`missing ${item.msg}`);
550
+ ok(item.msg);
551
+ }
552
+ const askUserIndex = join(ROOT, ".pi", "lib", "ask-user", "index.ts");
553
+ if (!(await fileExists(askUserIndex))) fail("missing .pi/lib/ask-user/index.ts");
554
+ const askUserSrc = await readFile(askUserIndex, "utf-8");
555
+ for (const sym of ASK_USER_PUBLIC_EXPORTS) {
556
+ if (!askUserSrc.includes(sym)) fail(`ask-user/index.ts missing export or symbol: ${sym}`);
557
+ }
558
+ ok("ask-user public API (index.ts)");
559
+ }
335
560
 
561
+ async function verifySubagentBridgeAndGovernance(pkgJson) {
336
562
  if (!pkgJson.files?.includes("vendor/pi-subagents")) {
337
- fail(
338
- 'package.json "files" must include vendor/pi-subagents (npm publish ships subagents vendor)',
339
- );
563
+ fail('package.json "files" must include vendor/pi-subagents (npm publish ships subagents vendor)');
340
564
  }
341
565
  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
- }
566
+ const subagentsVendor = join(ROOT, "vendor", "pi-subagents", "src", "subagents.ts");
567
+ if (!(await fileExists(subagentsVendor))) fail("missing vendor/pi-subagents/src/subagents.ts");
353
568
  const bridgePath = join(ROOT, ".pi", "lib", "harness-subagents-bridge.ts");
354
- if (!(await fileExists(bridgePath))) {
355
- fail("missing harness-subagents-bridge.ts");
356
- }
569
+ if (!(await fileExists(bridgePath))) fail("missing harness-subagents-bridge.ts");
357
570
  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");
571
+ const bridgeNeedles = [
572
+ "precheckHarnessSubagentSpawn",
573
+ "packageRoot",
574
+ ];
575
+ for (const needle of bridgeNeedles) {
576
+ if (!bridgeSrc.includes(needle)) fail(`harness-subagents-bridge missing required token: ${needle}`);
363
577
  }
364
- if (
365
- !bridgeSrc.includes("subprocessGovernanceExtensionPath") &&
366
- !bridgeSrc.includes("subagentGovernanceExtensionPath")
367
- ) {
368
- fail(
369
- "harness-subagents-bridge must set subprocessGovernanceExtensionPath for all subagents",
370
- );
578
+ if (!bridgeSrc.includes("subprocessGovernanceExtensionPath") && !bridgeSrc.includes("subagentGovernanceExtensionPath")) {
579
+ fail("harness-subagents-bridge must set subprocessGovernanceExtensionPath for all subagents");
371
580
  }
372
581
  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
- }
582
+ if (!subagentsSrc.includes("discoverAgents")) fail("vendor subagents.ts must implement discoverAgents");
583
+ if (!subagentsSrc.includes("packageRoot")) fail("vendor subagents.ts must pass packageRoot into discovery");
379
584
  ok("vendor pi-subagents + harness bridge");
380
585
 
381
- const policyGateSrc = await readFile(
382
- join(ROOT, ".pi", "extensions", "policy-gate.ts"),
383
- "utf-8",
384
- );
586
+ const policyGateSrc = await readFile(join(ROOT, ".pi", "extensions", "policy-gate.ts"), "utf-8");
385
587
  if (!policyGateSrc.includes("isPlanPhaseAllowedMutation")) {
386
- fail(
387
- "policy-gate.ts must use isPlanPhaseAllowedMutation (plan-phase scoped writes)",
388
- );
588
+ fail("policy-gate.ts must use isPlanPhaseAllowedMutation (plan-phase scoped writes)");
389
589
  }
390
590
  if (!policyGateSrc.includes('pi.on("tool_call", async (event, ctx)')) {
391
591
  fail("policy-gate tool_call must receive ctx for run context");
@@ -394,108 +594,144 @@ async function main() {
394
594
  fail("policy-gate.ts must delegate tool_call to AGT evaluateAgtHarnessToolCall");
395
595
  }
396
596
  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
- }
597
+ const govAlias = join(ROOT, ".pi", "extensions", "harness-subagent-governance.ts");
598
+ if (!(await fileExists(govPath))) fail("missing subagent-governance.ts subprocess bundle");
599
+ if (!(await fileExists(govAlias))) fail("missing harness-subagent-governance.ts re-export alias");
409
600
  ok("policy-gate + subprocess governance");
601
+ }
410
602
 
603
+ async function runAgtPolicyDoctor() {
411
604
  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
- }
605
+ const { code: agtDoctorCode, out: agtDoctorOut } = await new Promise((resolve) => {
606
+ const child = spawn("npx", ["-y", "tsx", agtDoctorPath], {
607
+ cwd: ROOT,
608
+ stdio: ["ignore", "pipe", "pipe"],
609
+ shell: true,
610
+ });
611
+ let out = "";
612
+ child.stdout?.on("data", (d) => {
613
+ out += d.toString();
614
+ });
615
+ child.stderr?.on("data", (d) => {
616
+ out += d.toString();
617
+ });
618
+ child.on("close", (code) => resolve({ code: code ?? 1, out }));
619
+ });
620
+ if (agtDoctorCode !== 0) fail(agtDoctorOut.trim() || "AGT policy doctor failed");
432
621
  ok("AGT policy doctor");
622
+ }
433
623
 
624
+ async function verifySmokeFixtures() {
434
625
  const runCtxFixture = join(SMOKE, "run-context.fixture.json");
435
- if (!(await fileExists(runCtxFixture))) {
436
- fail("missing run-context.fixture.json");
437
- }
626
+ if (!(await fileExists(runCtxFixture))) fail("missing run-context.fixture.json");
438
627
  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
- }
628
+ if (runCtxData.schema_version !== "1.0.0") fail("run-context fixture schema_version must be 1.0.0");
442
629
  if (!runCtxData.run_id) fail("run-context fixture missing run_id");
443
630
  ok("run-context.fixture.json");
444
631
 
445
- const fixture = JSON.parse(
446
- await readFile(join(SMOKE, "run-record.fixture.json"), "utf-8"),
447
- );
632
+ const fixture = JSON.parse(await readFile(join(SMOKE, "run-record.fixture.json"), "utf-8"));
448
633
  validateRunRecordFixture(fixture);
449
634
  ok("run-record.fixture.json");
450
635
 
451
- const golden = JSON.parse(
452
- await readFile(join(SMOKE, "test-diff-golden.json"), "utf-8"),
453
- );
636
+ const golden = JSON.parse(await readFile(join(SMOKE, "test-diff-golden.json"), "utf-8"));
454
637
  validateTestDiffGolden(golden);
455
638
  ok("test-diff-golden.json");
639
+ }
456
640
 
457
- await checkSentruxGate();
458
-
641
+ async function verifyAgentsPolicyAndManifest() {
459
642
  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
- }
643
+ if (!(await fileExists(AGENTS_POLICY))) fail("missing .pi/harness/agents.policy.yaml");
463
644
  ok("agents.policy.yaml present");
464
-
465
645
  const policyYaml = await readFile(AGENTS_POLICY, "utf8");
466
646
  if (!/^\s+extension_bundle:\s+executor/m.test(policyYaml)) {
467
647
  fail("agents.policy.yaml kinds.executor must set extension_bundle: executor");
468
648
  }
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
- );
649
+ if (/harness\/running\/executor:[\s\S]*?extensions:\s+true/m.test(policyYaml)) {
650
+ fail("harness/running/executor must not set extensions: true (use kind extension_bundle)");
475
651
  }
476
652
  ok("executor extension_bundle policy");
477
653
 
478
654
  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
- );
655
+ fail('missing .pi/harness/agents.manifest.json — run node "$UP_PKG/.pi/scripts/harness-agents-manifest.mjs" --write');
482
656
  }
483
657
  ok("agents.manifest.json present");
484
-
485
658
  const { code: manifestCode, out: manifestOut } = await runNodeScript(
486
659
  join(ROOT, ".pi", "scripts", "harness-agents-manifest.mjs"),
487
660
  ["--check"],
488
661
  );
489
- if (manifestCode !== 0) {
490
- fail(manifestOut.trim() || "agents.manifest.json drift — regenerate with --write");
491
- }
662
+ if (manifestCode !== 0) fail(manifestOut.trim() || "agents.manifest.json drift — regenerate with --write");
492
663
  ok("agents.manifest.json in sync");
664
+ }
493
665
 
666
+ async function main() {
667
+ console.log("harness:verify\n");
668
+ await verifySchemaAdrAndExtensions();
669
+ await verifyCoreSurfaceFiles();
670
+ await checkPromptFrontmatter();
671
+ await checkInternalPromptReferencePolicy();
672
+ const pkgJson = JSON.parse(await readFile(join(ROOT, "package.json"), "utf-8"));
673
+ await checkHarnessLens(pkgJson);
674
+ await checkHarnessAnchoredEdit(pkgJson);
675
+ await verifySubagentBridgeAndGovernance(pkgJson);
676
+ await runAgtPolicyDoctor();
677
+ await verifySmokeFixtures();
678
+ await checkSentruxGate();
679
+ await checkLsLintGate();
680
+ await verifyAgentsPolicyAndManifest();
681
+ await checkAutoCommitGitCommit();
494
682
  await checkWrsContracts();
495
-
496
683
  console.log("\nharness:verify PASS");
497
684
  }
498
685
 
686
+ async function checkAutoCommitGitCommit() {
687
+ const skill = join(
688
+ ROOT,
689
+ ".agents",
690
+ "skills",
691
+ "harness-git-commit",
692
+ "SKILL.md",
693
+ );
694
+ const script = join(ROOT, ".pi", "scripts", "harness-git-commit.mjs");
695
+ const lib = join(ROOT, ".pi", "lib", "harness-auto-commit-config.mjs");
696
+ const bootstrap = join(
697
+ ROOT,
698
+ ".pi",
699
+ "scripts",
700
+ "harness-auto-commit-bootstrap.mjs",
701
+ );
702
+ const autoCommit = join(ROOT, ".pi", "auto-commit.json");
703
+ for (const p of [skill, script, lib, bootstrap, autoCommit]) {
704
+ if (!(await fileExists(p))) {
705
+ fail(`missing auto-commit artifact: ${p}`);
706
+ }
707
+ }
708
+
709
+ const { validateAutoCommitConfig, resolveAutoCommitConfig } = await import(
710
+ join(ROOT, ".pi", "lib", "harness-auto-commit-config.mjs")
711
+ );
712
+ const pkgConfig = JSON.parse(await readFile(autoCommit, "utf-8"));
713
+ validateAutoCommitConfig(pkgConfig);
714
+ await resolveAutoCommitConfig(ROOT, ROOT);
715
+
716
+ const sys = await readFile(join(ROOT, ".pi", "SYSTEM.md"), "utf-8");
717
+ if (!sys.includes("harness-git-commit")) {
718
+ fail("SYSTEM.md must reference harness-git-commit skill for commits");
719
+ }
720
+
721
+ const { code, out } = await runNodeScript(script, [
722
+ "--print-message",
723
+ "--subject",
724
+ "harness-verify smoke",
725
+ ]);
726
+ if (code !== 0) {
727
+ fail(out.trim() || "harness-git-commit --print-message failed");
728
+ }
729
+ if (!out.includes("Co-authored-by:")) {
730
+ fail("harness-git-commit message missing Co-authored-by trailer");
731
+ }
732
+ ok("auto-commit git commit (skill, CLI, config, SYSTEM.md)");
733
+ }
734
+
499
735
  async function checkWrsContracts() {
500
736
  const systemMd = join(ROOT, ".pi", "SYSTEM.md");
501
737
  const toolsTs = join(ROOT, ".pi", "extensions", "harness-web-tools.ts");