@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 +1 -2
- package/src/core/redact.ts +1 -1
- package/src/core/test-runner.ts +4 -1
- package/src/state/config.ts +9 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jwilger/pi-development-system",
|
|
3
|
-
"version": "0.
|
|
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": {
|
package/src/core/redact.ts
CHANGED
|
@@ -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]")
|
package/src/core/test-runner.ts
CHANGED
|
@@ -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"
|
|
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.
|
package/src/state/config.ts
CHANGED
|
@@ -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, "
|
|
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 },
|