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.
- package/.agents/skills/harness-context/SKILL.md +3 -3
- package/.agents/skills/harness-debate-plan/SKILL.md +2 -2
- package/.agents/skills/harness-decisions/SKILL.md +68 -2
- package/.agents/skills/harness-eval/SKILL.md +1 -1
- package/.agents/skills/harness-git-commit/SKILL.md +72 -0
- package/.agents/skills/harness-governor/SKILL.md +6 -6
- package/.agents/skills/harness-ls-lint-setup/SKILL.md +59 -0
- package/.agents/skills/harness-orchestration/SKILL.md +4 -4
- package/.agents/skills/harness-plan/SKILL.md +14 -12
- package/.agents/skills/harness-review/SKILL.md +3 -3
- package/.agents/skills/harness-sentrux-repair/SKILL.md +48 -0
- package/.agents/skills/harness-sentrux-setup/SKILL.md +2 -2
- package/.agents/skills/harness-spec/SKILL.md +1 -1
- package/.agents/skills/harness-steer/SKILL.md +2 -2
- package/.agents/skills/posthog-analyst/SKILL.md +1 -1
- package/.agents/skills/sentrux/SKILL.md +6 -4
- package/.agents/skills/web-retrieval/SKILL.md +1 -1
- package/.agents/skills/wiki-save/SKILL.md +1 -1
- package/.pi/PACKAGING.md +6 -0
- package/.pi/SYSTEM.md +21 -3
- package/.pi/agents/harness/ls-lint-steward.md +49 -0
- package/.pi/agents/harness/planning/decompose.md +5 -5
- package/.pi/agents/harness/planning/execution-plan-author.md +1 -1
- package/.pi/agents/harness/planning/hypothesis-validator.md +1 -1
- package/.pi/agents/harness/planning/hypothesis.md +1 -1
- package/.pi/agents/harness/planning/plan-adversary.md +1 -1
- package/.pi/agents/harness/planning/plan-evaluator.md +2 -2
- package/.pi/agents/harness/planning/plan-synthesizer.md +2 -2
- package/.pi/agents/harness/planning/review-integrator.md +1 -1
- package/.pi/agents/harness/planning/sprint-contract-auditor.md +5 -5
- package/.pi/agents/harness/reviewing/evaluator.md +1 -1
- package/.pi/agents/harness/running/executor.md +2 -2
- package/.pi/agents/harness/sentrux-repair-advisor.md +50 -0
- package/.pi/agents/harness/sentrux-steward.md +2 -2
- package/.pi/agents/pi-pi/prompt-expert.md +17 -2
- package/.pi/auto-commit.json +9 -2
- package/.pi/extensions/debate-orchestrator.ts +3 -0
- package/.pi/extensions/harness-anchored-edit.ts +7 -9
- package/.pi/extensions/harness-ask-user.ts +13 -34
- package/.pi/extensions/harness-debate-tools.ts +43 -4
- package/.pi/extensions/harness-live-widget.ts +28 -19
- package/.pi/extensions/harness-run-context.ts +278 -115
- package/.pi/extensions/harness-web-tools.ts +598 -471
- package/.pi/extensions/ls-lint-rules-sync.ts +103 -0
- package/.pi/extensions/observation-bus.ts +4 -0
- package/.pi/extensions/policy-gate.ts +270 -229
- package/.pi/extensions/sentrux-rules-sync.ts +2 -0
- package/.pi/extensions/soundboard.ts +48 -48
- package/.pi/harness/README.md +4 -0
- package/.pi/harness/agents.manifest.json +24 -16
- package/.pi/harness/agents.policy.yaml +49 -82
- package/.pi/harness/docs/adrs/0052-ls-lint-naming-lifecycle.md +45 -0
- package/.pi/harness/docs/adrs/0052-sentrux-structured-repair.md +38 -0
- package/.pi/harness/docs/adrs/0053-plan-task-clarification-gate.md +39 -0
- package/.pi/harness/docs/adrs/0054-harness-native-ask-user.md +40 -0
- package/.pi/harness/docs/adrs/0055-auto-commit-coauthor-lifecycle.md +40 -0
- package/.pi/harness/docs/adrs/README.md +5 -0
- package/.pi/harness/docs/practice-map.md +10 -5
- package/.pi/harness/evals/smoke/ls-lint-stub.json +10 -0
- package/.pi/harness/evolution/self-healing-rules.json +16 -0
- package/.pi/harness/ls-lint/naming.manifest.json +128 -0
- package/.pi/harness/sentrux/architecture.manifest.json +1 -1
- package/.pi/harness/specs/auto-commit.schema.json +63 -0
- package/.pi/harness/specs/ls-lint-manifest-proposal.schema.json +80 -0
- package/.pi/harness/specs/ls-lint-signal.schema.json +47 -0
- package/.pi/harness/specs/naming-manifest.schema.json +54 -0
- package/.pi/harness/specs/plan-task-clarification.schema.json +88 -0
- package/.pi/harness/specs/sentrux-diagnostics.schema.json +173 -0
- package/.pi/harness/specs/sentrux-repair-plan.schema.json +133 -0
- package/.pi/harness/specs/sentrux-report.schema.json +119 -0
- package/.pi/harness/specs/sentrux-signal.schema.json +34 -1
- package/.pi/lib/agents-policy.d.mts +26 -51
- package/.pi/lib/agents-policy.mjs +41 -28
- package/.pi/lib/agt/build-evaluation-context.ts +136 -64
- package/.pi/lib/ask-user/constants.mjs +3 -0
- package/.pi/lib/ask-user/constants.ts +4 -0
- package/.pi/lib/ask-user/contracts/glimpse-parse.ts +56 -0
- package/.pi/lib/ask-user/contracts/glimpse-payload-build.ts +58 -0
- package/.pi/lib/ask-user/contracts/glimpse-payload.ts +38 -0
- package/.pi/lib/ask-user/core/questionnaire.ts +74 -0
- package/.pi/lib/ask-user/dialog.ts +2 -314
- package/.pi/lib/ask-user/fallback.ts +2 -78
- package/.pi/lib/ask-user/format.ts +85 -0
- package/.pi/lib/ask-user/glimpseui.d.ts +10 -0
- package/.pi/lib/ask-user/index.ts +114 -0
- package/.pi/lib/ask-user/merge-task-clarification.ts +98 -0
- package/.pi/lib/ask-user/policy.mjs +43 -0
- package/.pi/lib/ask-user/policy.ts +104 -0
- package/.pi/lib/ask-user/presenters/glimpse.ts +130 -0
- package/.pi/lib/ask-user/presenters/headless.ts +131 -0
- package/.pi/lib/ask-user/presenters/select.ts +60 -0
- package/.pi/lib/ask-user/presenters/tui.ts +373 -0
- package/.pi/lib/ask-user/presenters/types.ts +13 -0
- package/.pi/lib/ask-user/render.ts +40 -9
- package/.pi/lib/ask-user/schema.ts +66 -13
- package/.pi/lib/ask-user/types.ts +60 -3
- package/.pi/lib/ask-user/validate-core.mjs +193 -7
- package/.pi/lib/ask-user/validate.ts +53 -34
- package/.pi/lib/harness-anchored-edit/package.json +3 -0
- package/.pi/lib/harness-artifact-gate.ts +75 -21
- package/.pi/lib/harness-auto-commit-config.mjs +321 -0
- package/.pi/lib/harness-lens/clients/lsp/client.ts +62 -39
- package/.pi/lib/harness-lens/clients/tool-policy.ts +73 -181
- package/.pi/lib/harness-lens/index.ts +241 -108
- package/.pi/lib/harness-lens/tools/lsp-navigation.ts +10 -8
- package/.pi/lib/harness-repair-brief.ts +84 -25
- package/.pi/lib/harness-run-context.ts +42 -52
- package/.pi/lib/harness-sentrux-parse.mjs +272 -0
- package/.pi/lib/harness-sentrux-root.mjs +78 -0
- package/.pi/lib/harness-slash-completions.ts +116 -0
- package/.pi/lib/harness-spawn-topology.ts +121 -87
- package/.pi/lib/harness-subagent-submit-registry.ts +10 -0
- package/.pi/lib/harness-subagents-bridge.ts +4 -1
- package/.pi/lib/harness-ui-state.ts +95 -48
- package/.pi/lib/plan-approval/dialog.ts +5 -0
- package/.pi/lib/plan-approval/validate.ts +1 -1
- package/.pi/lib/plan-approval-readiness.ts +32 -0
- package/.pi/lib/plan-debate-gate.ts +154 -114
- package/.pi/lib/plan-task-clarification.ts +158 -0
- package/.pi/prompts/harness-auto.md +2 -2
- package/.pi/prompts/harness-ls-lint-steward.md +43 -0
- package/.pi/prompts/harness-plan.md +63 -13
- package/.pi/prompts/harness-review.md +44 -10
- package/.pi/prompts/harness-run.md +35 -13
- package/.pi/prompts/harness-sentrux-steward.md +2 -2
- package/.pi/prompts/harness-setup.md +74 -5
- package/.pi/prompts/harness-steer.md +6 -5
- package/.pi/prompts/wiki-save.md +5 -4
- package/.pi/scripts/README.md +8 -0
- package/.pi/scripts/generate-agents-policy-yaml.mjs +14 -2
- package/.pi/scripts/harness-auto-commit-bootstrap.mjs +96 -0
- package/.pi/scripts/harness-cli-verify.sh +47 -0
- package/.pi/scripts/harness-git-churn.mjs +77 -0
- package/.pi/scripts/harness-git-commit.mjs +173 -0
- package/.pi/scripts/harness-ls-lint-bootstrap.mjs +142 -0
- package/.pi/scripts/harness-ls-lint-cli.mjs +184 -0
- package/.pi/scripts/harness-seed-project-contracts.mjs +47 -0
- package/.pi/scripts/harness-sentrux-diagnostics.mjs +230 -0
- package/.pi/scripts/harness-sentrux-report.mjs +256 -0
- package/.pi/scripts/harness-verify.mjs +361 -125
- package/.pi/scripts/ls-lint-rules-sync.mjs +265 -0
- package/.pi/scripts/run-tests.mjs +1 -0
- package/.pi/settings.example.json +1 -0
- package/.sentrux/rules.toml +1 -1
- package/AGENTS.md +2 -0
- package/CHANGELOG.md +32 -0
- package/README.md +13 -4
- package/package.json +13 -6
- 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
|
|
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
await
|
|
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
|
-
|
|
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
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
399
|
-
|
|
400
|
-
"extensions",
|
|
401
|
-
"harness-subagent-governance.ts",
|
|
402
|
-
);
|
|
403
|
-
if (!(await fileExists(govPath))) {
|
|
404
|
-
fail("missing subagent-governance.ts subprocess bundle");
|
|
405
|
-
}
|
|
406
|
-
if (!(await fileExists(govAlias))) {
|
|
407
|
-
fail("missing harness-subagent-governance.ts re-export alias");
|
|
408
|
-
}
|
|
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
|
-
(
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
);
|
|
429
|
-
if (agtDoctorCode !== 0) {
|
|
430
|
-
fail(agtDoctorOut.trim() || "AGT policy doctor failed");
|
|
431
|
-
}
|
|
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
|
-
|
|
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
|
-
/
|
|
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");
|