@tryinget/pi-evalset-lab 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -13,6 +13,20 @@ system4d:
13
13
 
14
14
  All notable changes to this project should be documented here.
15
15
 
16
+ ## [0.3.0](https://github.com/tryingET/pi-extensions/compare/pi-evalset-lab-v0.2.0...pi-evalset-lab-v0.3.0) (2026-07-13)
17
+
18
+
19
+ ### Features
20
+
21
+ * adopt engineering-core package surfaces ([c4a28c1](https://github.com/tryingET/pi-extensions/commit/c4a28c12c6077ba5b17909bcde3354bb1249e8d0))
22
+ * **extensions:** harden runtime quality across packages ([1ff1eb0](https://github.com/tryingET/pi-extensions/commit/1ff1eb0cf10a3f8f60cf391ccf246b238951a848))
23
+ * migrate pi extensions to pi 0.76 ([93dd0e0](https://github.com/tryingET/pi-extensions/commit/93dd0e0fdc9e23b0fc36661cc0e33972f365bf98))
24
+
25
+
26
+ ### Bug Fixes
27
+
28
+ * align extension runtimes with Pi 0.80 ([1702c25](https://github.com/tryingET/pi-extensions/commit/1702c25f9d31bf4f619fdaa6f7a7f898ca5ee48e))
29
+
16
30
  ## [Unreleased]
17
31
 
18
32
  ### Changed
@@ -20,7 +34,7 @@ All notable changes to this project should be documented here.
20
34
  - Canonicalized the legacy standalone `pi-evalset-lab` package into the `pi-extensions` monorepo at `packages/pi-evalset-lab`.
21
35
  - Switched npm identity to scoped monorepo package name `@tryinget/pi-evalset-lab` while preserving legacy runtime version `0.2.0`.
22
36
  - Moved release automation to root-managed component metadata (`pi-evalset-lab`).
23
- - Archived the former standalone working copy to `~/programming/pi-extensions/pi-evalset-lab-final-archive.tar.gz` and removed it after validation; no legacy Pi session-history directory existed to relocate.
37
+ - Removed the former standalone `~/programming/pi-extensions/` workspace after canonicalization; no legacy Pi session-history directory existed for `pi-evalset-lab` to relocate.
24
38
 
25
39
  ## [0.2.0](https://github.com/tryingET/pi-evalset-lab/compare/v0.1.0...v0.2.0) (2026-02-17)
26
40
 
package/README.md CHANGED
@@ -17,8 +17,8 @@ Monorepo package for fixed-task-set eval workflows in Pi (`/evalset run|compare`
17
17
  - Workspace path: `packages/pi-evalset-lab`
18
18
  - Release component key: `pi-evalset-lab`
19
19
  - Former legacy standalone source: `~/programming/pi-extensions/pi-evalset-lab`
20
- - Canonical package status: canonicalized here; the legacy repo was archived to `~/programming/pi-extensions/pi-evalset-lab-final-archive.tar.gz` and removed after validation.
21
- - Session-history migration: no legacy Pi session-history directory existed for the old path, so relocation was recorded as `skip-no-history`.
20
+ - Canonical package status: canonicalized here; the former `~/programming/pi-extensions/` workspace was later deleted at operator request.
21
+ - Session-history migration: no legacy Pi session-history directory existed for the old package path, so relocation was recorded as `skip-no-history`.
22
22
 
23
23
  Primary category fit: **Model & Prompt Management**, **Review & Quality Loops**, **UX & Observability**, **Safety & Governance**.
24
24
 
@@ -26,8 +26,8 @@ Primary category fit: **Model & Prompt Management**, **Review & Quality Loops**,
26
26
 
27
27
  This package expects Pi host runtime APIs and declares them as `peerDependencies`:
28
28
 
29
- - `@mariozechner/pi-coding-agent`
30
- - `@mariozechner/pi-ai`
29
+ - `@earendil-works/pi-coding-agent`
30
+ - `@earendil-works/pi-ai`
31
31
 
32
32
  The npm package uses a `files` whitelist so required runtime artifacts are explicitly included:
33
33
 
@@ -1,14 +1,15 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
- import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { link, mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
3
  import { dirname, isAbsolute, resolve } from "node:path";
4
4
  import {
5
+ type Api,
5
6
  type AssistantMessage,
6
7
  type Context,
7
8
  complete,
8
9
  type Model,
9
10
  type Usage,
10
- } from "@mariozechner/pi-ai";
11
- import type { ExtensionAPI, ExtensionCommandContext } from "@mariozechner/pi-coding-agent";
11
+ } from "@earendil-works/pi-ai/compat";
12
+ import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
12
13
 
13
14
  const COMMAND_NAME = "evalset";
14
15
  const CUSTOM_MESSAGE_TYPE = "evalset";
@@ -97,6 +98,7 @@ interface EvalCaseResult {
97
98
  pass: boolean;
98
99
  checks: CaseCheckResult[];
99
100
  failedChecks: string[];
101
+ output: string;
100
102
  outputPreview: string;
101
103
  latencyMs: number;
102
104
  stopReason: string;
@@ -296,11 +298,20 @@ function parseDataset(raw: string): EvalDataset {
296
298
  throw new Error("Dataset field 'systemPrompt' must be a string when provided.");
297
299
  }
298
300
 
299
- return {
300
- name,
301
- systemPrompt,
302
- cases: cases.map((entry, index) => parseCase(entry, index)),
303
- };
301
+ const parsedCases = cases.map((entry, index) => parseCase(entry, index));
302
+ const ids = new Set<string>();
303
+ for (let index = 0; index < parsedCases.length; index += 1) {
304
+ const id = parsedCases[index].id;
305
+ if (id !== undefined && !id.trim()) {
306
+ throw new Error(`Case at index ${index}: 'id' must not be empty.`);
307
+ }
308
+ const normalized = id?.trim() || `case-${index + 1}`;
309
+ if (ids.has(normalized)) throw new Error(`Duplicate case id: ${normalized}`);
310
+ ids.add(normalized);
311
+ parsedCases[index].id = normalized;
312
+ }
313
+
314
+ return { name, systemPrompt, cases: parsedCases };
304
315
  }
305
316
 
306
317
  function toAbsolutePath(cwd: string, inputPath: string): string {
@@ -345,15 +356,17 @@ function requireValue(tokens: string[], index: number, flag: string): string {
345
356
  }
346
357
 
347
358
  function parsePositiveInteger(raw: string, field: string): number {
348
- const parsed = Number.parseInt(raw, 10);
349
- if (!Number.isFinite(parsed) || parsed <= 0) {
350
- throw new Error(`${field} must be a positive integer.`);
351
- }
359
+ if (!/^[1-9]\d*$/.test(raw)) throw new Error(`${field} must be a positive integer.`);
360
+ const parsed = Number(raw);
361
+ if (!Number.isSafeInteger(parsed)) throw new Error(`${field} must be a positive integer.`);
352
362
  return parsed;
353
363
  }
354
364
 
355
365
  function parseTemperature(raw: string): number {
356
- const parsed = Number.parseFloat(raw);
366
+ if (!/^(?:\d+(?:\.\d*)?|\.\d+)$/.test(raw)) {
367
+ throw new Error("--temperature must be a number between 0 and 2.");
368
+ }
369
+ const parsed = Number(raw);
357
370
  if (!Number.isFinite(parsed) || parsed < 0 || parsed > 2) {
358
371
  throw new Error("--temperature must be a number between 0 and 2.");
359
372
  }
@@ -633,7 +646,7 @@ function defaultRunReportPath(cwd: string, datasetName: string, variantName: str
633
646
  cwd,
634
647
  ".evalset",
635
648
  "reports",
636
- `run-${sanitizeSlug(datasetName)}-${sanitizeSlug(variantName)}-${timestampSlug()}.json`,
649
+ `run-${sanitizeSlug(datasetName)}-${sanitizeSlug(variantName)}-${timestampSlug()}-${randomUUID().slice(0, 8)}.json`,
637
650
  );
638
651
  }
639
652
 
@@ -642,19 +655,28 @@ function defaultCompareReportPath(cwd: string, datasetName: string): string {
642
655
  cwd,
643
656
  ".evalset",
644
657
  "reports",
645
- `compare-${sanitizeSlug(datasetName)}-${timestampSlug()}.json`,
658
+ `compare-${sanitizeSlug(datasetName)}-${timestampSlug()}-${randomUUID().slice(0, 8)}.json`,
646
659
  );
647
660
  }
648
661
 
649
662
  async function writeReportFile(path: string, data: unknown): Promise<string> {
650
663
  await mkdir(dirname(path), { recursive: true });
651
- await writeFile(path, `${JSON.stringify(data, null, 2)}\n`, "utf8");
664
+ const tempPath = `${path}.tmp-${process.pid}-${randomUUID()}`;
665
+ try {
666
+ await writeFile(tempPath, `${JSON.stringify(data, null, 2)}\n`, {
667
+ encoding: "utf8",
668
+ flag: "wx",
669
+ });
670
+ await link(tempPath, path);
671
+ } finally {
672
+ await rm(tempPath, { force: true }).catch(() => undefined);
673
+ }
652
674
  return path;
653
675
  }
654
676
 
655
677
  async function evaluateVariant(args: {
656
678
  ctx: ExtensionCommandContext;
657
- model: Model;
679
+ model: Model<Api>;
658
680
  datasetPath: string;
659
681
  datasetName: string;
660
682
  datasetHash: string;
@@ -662,6 +684,7 @@ async function evaluateVariant(args: {
662
684
  cases: EvalCaseDefinition[];
663
685
  variant: VariantDefinition;
664
686
  apiKey?: string;
687
+ headers?: Record<string, string>;
665
688
  temperature?: number;
666
689
  }): Promise<EvalRunReport> {
667
690
  const {
@@ -674,6 +697,7 @@ async function evaluateVariant(args: {
674
697
  cases,
675
698
  variant,
676
699
  apiKey,
700
+ headers,
677
701
  temperature,
678
702
  } = args;
679
703
  const runId = randomUUID();
@@ -709,6 +733,7 @@ async function evaluateVariant(args: {
709
733
  try {
710
734
  const response = await complete(model, context, {
711
735
  apiKey,
736
+ headers,
712
737
  temperature,
713
738
  });
714
739
 
@@ -724,6 +749,7 @@ async function evaluateVariant(args: {
724
749
  failedChecks: evaluation.checks
725
750
  .filter((check) => !check.pass)
726
751
  .map((check) => check.details),
752
+ output: outputText,
727
753
  outputPreview: clip(outputText),
728
754
  latencyMs: Date.now() - startedAt,
729
755
  stopReason: response.stopReason,
@@ -744,6 +770,7 @@ async function evaluateVariant(args: {
744
770
  },
745
771
  ],
746
772
  failedChecks: [message],
773
+ output: "",
747
774
  outputPreview: "",
748
775
  latencyMs: Date.now() - startedAt,
749
776
  stopReason: "error",
@@ -826,13 +853,22 @@ function postMessage(pi: ExtensionAPI, message: string, details?: unknown): void
826
853
  });
827
854
  }
828
855
 
829
- function ensureActiveModel(ctx: ExtensionCommandContext): Model {
856
+ function ensureActiveModel(ctx: ExtensionCommandContext): Model<Api> {
830
857
  if (!ctx.model) {
831
858
  throw new Error("No active model. Select one first via /model.");
832
859
  }
833
860
  return ctx.model;
834
861
  }
835
862
 
863
+ async function resolveRequestAuth(
864
+ ctx: ExtensionCommandContext,
865
+ model: Model<Api>,
866
+ ): Promise<{ apiKey?: string; headers?: Record<string, string> }> {
867
+ const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
868
+ if (!auth.ok) throw new Error(auth.error);
869
+ return { apiKey: auth.apiKey, headers: auth.headers };
870
+ }
871
+
836
872
  async function handleInit(
837
873
  pi: ExtensionAPI,
838
874
  ctx: ExtensionCommandContext,
@@ -897,7 +933,7 @@ async function handleRun(
897
933
  ): Promise<void> {
898
934
  const config = parseRunCommand(tokens);
899
935
  const model = ensureActiveModel(ctx);
900
- const apiKey = await ctx.modelRegistry.getApiKey(model);
936
+ const requestAuth = await resolveRequestAuth(ctx, model);
901
937
 
902
938
  const loaded = await loadDataset(ctx.cwd, config.datasetPath);
903
939
  const datasetName = loaded.dataset.name?.trim() || sanitizeSlug(config.datasetPath);
@@ -935,7 +971,7 @@ async function handleRun(
935
971
  casesHash,
936
972
  cases: datasetCases,
937
973
  variant,
938
- apiKey,
974
+ ...requestAuth,
939
975
  temperature: config.temperature,
940
976
  });
941
977
 
@@ -974,7 +1010,7 @@ async function handleCompare(
974
1010
  ): Promise<void> {
975
1011
  const config = parseCompareCommand(tokens);
976
1012
  const model = ensureActiveModel(ctx);
977
- const apiKey = await ctx.modelRegistry.getApiKey(model);
1013
+ const requestAuth = await resolveRequestAuth(ctx, model);
978
1014
 
979
1015
  const loaded = await loadDataset(ctx.cwd, config.datasetPath);
980
1016
  const datasetName = loaded.dataset.name?.trim() || sanitizeSlug(config.datasetPath);
@@ -1012,7 +1048,7 @@ async function handleCompare(
1012
1048
  casesHash,
1013
1049
  cases: datasetCases,
1014
1050
  variant: baselineVariant,
1015
- apiKey,
1051
+ ...requestAuth,
1016
1052
  temperature: config.temperature,
1017
1053
  });
1018
1054
 
@@ -1025,7 +1061,7 @@ async function handleCompare(
1025
1061
  casesHash,
1026
1062
  cases: datasetCases,
1027
1063
  variant: candidateVariant,
1028
- apiKey,
1064
+ ...requestAuth,
1029
1065
  temperature: config.temperature,
1030
1066
  });
1031
1067
 
@@ -1107,6 +1143,14 @@ function formatError(error: unknown): string {
1107
1143
  return String(error);
1108
1144
  }
1109
1145
 
1146
+ export const _test = {
1147
+ parseDataset,
1148
+ parsePositiveInteger,
1149
+ parseTemperature,
1150
+ parseRunCommand,
1151
+ writeReportFile,
1152
+ };
1153
+
1110
1154
  export default function (pi: ExtensionAPI): void {
1111
1155
  pi.registerCommand(COMMAND_NAME, {
1112
1156
  description: "Run fixed-task-set evals and compare prompt/system variants",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tryinget/pi-evalset-lab",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "pi extension for fixed-task-set eval runs and prompt/system comparisons",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -31,7 +31,7 @@
31
31
  "access": "public"
32
32
  },
33
33
  "engines": {
34
- "node": ">=22"
34
+ "node": ">=22.19.0"
35
35
  },
36
36
  "scripts": {
37
37
  "fix": "bash ./scripts/quality-gate.sh fix",
@@ -47,7 +47,13 @@
47
47
  "docs:list:json": "bash ./scripts/docs-list.sh --json",
48
48
  "release:check": "bash ./scripts/release-check.sh",
49
49
  "release:check:quick": "SKIP_PI_SMOKE=1 bash ./scripts/release-check.sh",
50
- "evalset:export-html": "node ./scripts/export-evalset-report-html.mjs"
50
+ "evalset:export-html": "node ./scripts/export-evalset-report-html.mjs",
51
+ "loop-doctor": "bash -lc 'node --version; npm --version; npm pkg get name version >/dev/null; git status --short -- . || true; exit 0'",
52
+ "loop-verify-fast": "npm run quality:pre-commit",
53
+ "loop-impact-plan": "bash -lc 'echo \"loop-impact-plan: package-local impact planner is coarse; run npm run loop-impact-run for the full package gate.\"; git status --short -- . || true'",
54
+ "loop-impact-run": "npm run check",
55
+ "loop-impact-wide": "npm run check",
56
+ "loop-landing-check": "npm run check"
51
57
  },
52
58
  "files": [
53
59
  "extensions/evalset.ts",
@@ -56,7 +62,7 @@
56
62
  "scripts/export-evalset-report-html.mjs",
57
63
  "CHANGELOG.md",
58
64
  "policy/security-policy.json",
59
- "policy/stack-lane.json"
65
+ "policy/engineering-lane.json"
60
66
  ],
61
67
  "pi": {
62
68
  "extensions": [
@@ -73,13 +79,17 @@
73
79
  "releaseConfigMode": "component"
74
80
  },
75
81
  "devDependencies": {
76
- "@biomejs/biome": "2.3.14"
82
+ "@biomejs/biome": "2.3.14",
83
+ "@earendil-works/pi-ai": "^0.80.6",
84
+ "@earendil-works/pi-coding-agent": "^0.80.6",
85
+ "@types/node": "^25.9.4",
86
+ "typescript": "^6.0.3"
77
87
  },
78
88
  "overrides": {
79
89
  "fast-xml-parser": "5.3.6"
80
90
  },
81
91
  "peerDependencies": {
82
- "@mariozechner/pi-coding-agent": "*",
83
- "@mariozechner/pi-ai": "*"
92
+ "@earendil-works/pi-ai": "*",
93
+ "@earendil-works/pi-coding-agent": "*"
84
94
  }
85
95
  }
@@ -0,0 +1,36 @@
1
+ {
2
+ "lane": "ts",
3
+ "engineering_core": {
4
+ "tool": "engineering-core",
5
+ "lane": "pi-ts",
6
+ "repository": "https://github.com/lightningralf/engineering-core",
7
+ "ref": "workspace-local-unpinned",
8
+ "command": "uv tool -n run --from ~/ai-society/core/engineering-core engineering-core show pi-ts --prefer-repo",
9
+ "catalog_command": "uv tool -n run --from ~/ai-society/core/engineering-core engineering-core catalog --pretty",
10
+ "list_disciplines_command": "uv tool -n run --from ~/ai-society/core/engineering-core engineering-core list-disciplines",
11
+ "list_templates_command": "uv tool -n run --from ~/ai-society/core/engineering-core engineering-core list-templates",
12
+ "disciplines": [
13
+ "validation",
14
+ "testing",
15
+ "security-privacy",
16
+ "documentation",
17
+ "dependency-governance",
18
+ "local-first-data",
19
+ "observability",
20
+ "specification-and-dsls",
21
+ "engineering-reasoning"
22
+ ],
23
+ "loop_validation": {
24
+ "version": "repo-loop-validation-v1",
25
+ "contract_doc": "docs/engineering.local.md#repo-loop-validation",
26
+ "commands": {
27
+ "loop-doctor": "npm run loop-doctor",
28
+ "loop-verify-fast": "npm run loop-verify-fast",
29
+ "loop-impact-plan": "npm run loop-impact-plan",
30
+ "loop-impact-run": "npm run loop-impact-run",
31
+ "loop-impact-wide": "npm run loop-impact-wide",
32
+ "loop-landing-check": "npm run loop-landing-check"
33
+ }
34
+ }
35
+ }
36
+ }
@@ -1,9 +1,9 @@
1
1
  ---
2
- summary: "Draft an implementation plan for a requested change"
2
+ summary: "Draft an implementation plan for a requested pi-evalset-lab change"
3
3
  read_when:
4
4
  - "Using or maintaining this package prompt template."
5
5
  - "Checking prompt metadata for pi package discovery or docs-list validation."
6
- description: Draft an implementation plan for a requested change
6
+ description: Draft an implementation plan for a requested pi-evalset-lab change
7
7
  system4d:
8
8
  container: "Prompt template for implementation planning."
9
9
  compass: "Turn requests into actionable, risk-aware plans."
@@ -11,7 +11,7 @@ system4d:
11
11
  fog: "Hidden constraints unless assumptions are surfaced."
12
12
  ---
13
13
 
14
- Create an implementation plan for this request: $@
14
+ Create a pi-evalset-lab implementation plan for this request: $@
15
15
 
16
16
  Include:
17
17
  - Scope and non-goals
@@ -1,9 +1,9 @@
1
1
  ---
2
- summary: "Review a change for security risks and mitigations"
2
+ summary: "Review a pi-evalset-lab change for security risks and mitigations"
3
3
  read_when:
4
4
  - "Using or maintaining this package prompt template."
5
5
  - "Checking prompt metadata for pi package discovery or docs-list validation."
6
- description: Review a change for security risks and mitigations
6
+ description: Review a pi-evalset-lab change for security risks and mitigations
7
7
  system4d:
8
8
  container: "Prompt template for security-focused review."
9
9
  compass: "Identify practical vulnerabilities before release."
@@ -11,7 +11,7 @@ system4d:
11
11
  fog: "Partial context can hide exploit paths."
12
12
  ---
13
13
 
14
- Review this change for security concerns: $@
14
+ Review this pi-evalset-lab change for security concerns: $@
15
15
 
16
16
  Focus on:
17
17
  - Input validation and injection risk
@@ -1,10 +0,0 @@
1
- {
2
- "lane": "ts",
3
- "tech_stack_core": {
4
- "tool": "tech-stack-core",
5
- "lane": "pi-ts",
6
- "repository": "https://github.com/lightningralf/tech-stack-core",
7
- "ref": "4bd813dbb9ebbd9cb279c7293cd941fd1154d59e",
8
- "command": "uv tool run --from ~/ai-society/core/tech-stack-core tech-stack-core show pi-ts --prefer-repo"
9
- }
10
- }