@jwilger/pi-development-system 0.45.1 → 0.46.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jwilger/pi-development-system",
3
- "version": "0.45.1",
3
+ "version": "0.46.0",
4
4
  "description": "A pi extension package representing a seasoned approach to software development using a full AI SDLC.",
5
5
  "keywords": [
6
6
  "pi-package"
@@ -52,7 +52,6 @@
52
52
  "@types/shell-quote": "^1.7.5",
53
53
  "@typesafe-ai/sdk": "^0.6.0",
54
54
  "knip": "^6.40.0",
55
- "markdownlint-cli2": "^0.23.3",
56
55
  "typescript": "^7.0.2"
57
56
  },
58
57
  "scripts": {
@@ -1,7 +1,7 @@
1
1
  /** Removes credentials from text before it is written to committed logs or session entries (R11). */
2
2
  export function redactSecrets(text: string): string {
3
3
  return text
4
- .replace(/(\w+:\/\/)[^\s/@]+@/g, "$1[redacted]@")
4
+ .replace(/(?<!\w)(\w+:\/\/)[^\s/@]+@/g, "$1[redacted]@")
5
5
  .replace(/\b(?:ghp|gho|ghu|ghs|github_pat)_[A-Za-z0-9_]{16,}/g, "[redacted]")
6
6
  .replace(/\b(\w*(?:TOKEN|KEY|SECRET|PASSWORD)\w*)=("[^"]*"|'[^']*'|\S+)/gi, "$1=[redacted]")
7
7
  .replace(/\b(Authorization:\s*)\w+\s+[^\s'"]+/gi, "$1[redacted]")
@@ -2,7 +2,7 @@ import { basename, isAssignment, segments, splitLines, WRAPPERS } from "./git-in
2
2
  import { redactSecrets } from "./redact.ts";
3
3
 
4
4
  const PACKAGE_MANAGERS = new Set(["npm", "pnpm", "yarn", "bun"]);
5
- const DIRECT_RUNNERS = new Set(["vitest", "jest", "pytest", "mocha", "ava", "tap"]);
5
+ const DIRECT_RUNNERS = new Set(["vitest", "jest", "pytest", "mocha", "ava"]);
6
6
  const EXEC_PREFIX = new Set(["npx", "bunx", "pnpx"]);
7
7
  const SUMMARY_LINES = 3;
8
8
  const SUMMARY_MAX = 300;
@@ -59,6 +59,7 @@ export type ResultLike = {
59
59
  };
60
60
 
61
61
  /** A pipe or sequence makes the shell report the last command's status, not the runner's. */
62
+ const NO_TEST_SCRIPT = /Missing script: "?test/;
62
63
  const MASKS_STATUS = /[|;\n]/;
63
64
  const FAILURE_MARKERS =
64
65
  /^(?:# |ℹ )fail [1-9]|test result: FAILED|\b[1-9]\d* (?:failed|failing)\b|^FAILED\b|^FAIL\b|\bnot ok\b/m;
@@ -68,6 +69,8 @@ const isRecord = (v: unknown): v is Record<string, unknown> =>
68
69
 
69
70
  /** Exit code of a bash tool result: structured `exit_code`, else pi's status line, else error/not. */
70
71
  export function exitCodeOf(result: ResultLike): number {
72
+ // `npm test` in a package without a test script exits 1 but ran no tests, so it is not a RED.
73
+ if (NO_TEST_SCRIPT.test(result.text)) return 0;
71
74
  const reported = reportedExit(result);
72
75
  if (reported !== 0) return reported;
73
76
  // `npm test | tail` exits 0 whatever the tests did, even in the structured status; trust the output.
@@ -31,7 +31,6 @@ export type DevsysConfig = {
31
31
  readonly profiles: { readonly override: readonly string[] };
32
32
  readonly models: ModelMatrix;
33
33
  readonly routing: Readonly<Record<string, Route>>;
34
- readonly jev: { readonly timeoutMs: number; readonly confidenceFloor: number };
35
34
  readonly verifier: { readonly maxPerSession: number };
36
35
  readonly cadence: { readonly pushMinutes: number };
37
36
  readonly eventModel: { readonly provider: string };
@@ -170,12 +169,20 @@ function route(raw: unknown, key: string): Result<Route, ConfigError> {
170
169
  : ok({ slot, thinkingLevel });
171
170
  }
172
171
 
172
+ const ROUTING_KEY = /^(?:trivial|routine|complex|expert|\*)\/(?:low|medium|high|\*)$/;
173
+
173
174
  function routing(root: Table): Result<Readonly<Record<string, Route>>, ConfigError> {
174
175
  const raw = root.routing;
175
176
  if (raw === undefined) return ok(DEFAULT_ROUTING);
176
177
  if (!isTable(raw)) return fail("routing must be a table", "routing");
177
178
  const result: Record<string, Route> = {};
178
179
  for (const [pattern, value] of Object.entries(raw)) {
180
+ if (!ROUTING_KEY.test(pattern)) {
181
+ return fail(
182
+ `routing.${pattern}: key must be <difficulty>/<risk>, each a level or *`,
183
+ `routing.${pattern}`,
184
+ );
185
+ }
179
186
  const parsed = route(value, `routing.${pattern}`);
180
187
  if (!parsed.ok) return parsed;
181
188
  result[pattern] = parsed.value;
@@ -201,7 +208,6 @@ const TOP_LEVEL = [
201
208
  "profiles",
202
209
  "models",
203
210
  "routing",
204
- "jev",
205
211
  "verifier",
206
212
  "cadence",
207
213
  "event_model",
@@ -237,27 +243,20 @@ function deliveryAndReview(
237
243
 
238
244
  function limits(
239
245
  root: Table,
240
- ): Result<Pick<DevsysConfig, "jev" | "verifier" | "cadence" | "eventModel">, ConfigError> {
241
- const j = section(root, "jev", ["timeout_ms", "confidence_floor"]);
246
+ ): Result<Pick<DevsysConfig, "verifier" | "cadence" | "eventModel">, ConfigError> {
242
247
  const v = section(root, "verifier", ["max_per_session"]);
243
248
  const c = section(root, "cadence", ["push_minutes"]);
244
249
  const e = section(root, "event_model", ["provider"]);
245
- if (!j.ok) return j;
246
250
  if (!v.ok) return v;
247
251
  if (!c.ok) return c;
248
252
  if (!e.ok) return e;
249
- const timeout = number(j.value, "jev", "timeout_ms", 4000, POSITIVE);
250
- const floor = number(j.value, "jev", "confidence_floor", 0.6, { min: 0, max: 1, integer: false });
251
253
  const max = number(v.value, "verifier", "max_per_session", 6, POSITIVE);
252
254
  const push = number(c.value, "cadence", "push_minutes", 60, POSITIVE);
253
255
  const provider = text(e.value, "event_model", "provider", "builtin");
254
- if (!timeout.ok) return timeout;
255
- if (!floor.ok) return floor;
256
256
  if (!max.ok) return max;
257
257
  if (!push.ok) return push;
258
258
  if (!provider.ok) return provider;
259
259
  return ok({
260
- jev: { timeoutMs: timeout.value, confidenceFloor: floor.value },
261
260
  verifier: { maxPerSession: max.value },
262
261
  cadence: { pushMinutes: push.value },
263
262
  eventModel: { provider: provider.value },