prompt-lock 0.2.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 +65 -0
- package/LICENSE +21 -0
- package/README.md +351 -0
- package/dist/assertions/builtin.d.ts +5 -0
- package/dist/assertions/builtin.d.ts.map +1 -0
- package/dist/assertions/builtin.js +172 -0
- package/dist/assertions/builtin.js.map +1 -0
- package/dist/assertions/custom.d.ts +3 -0
- package/dist/assertions/custom.d.ts.map +1 -0
- package/dist/assertions/custom.js +26 -0
- package/dist/assertions/custom.js.map +1 -0
- package/dist/assertions/index.d.ts +3 -0
- package/dist/assertions/index.d.ts.map +1 -0
- package/dist/assertions/index.js +32 -0
- package/dist/assertions/index.js.map +1 -0
- package/dist/assertions/json-schema.d.ts +3 -0
- package/dist/assertions/json-schema.d.ts.map +1 -0
- package/dist/assertions/json-schema.js +35 -0
- package/dist/assertions/json-schema.js.map +1 -0
- package/dist/cache.d.ts +14 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +114 -0
- package/dist/cache.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +434 -0
- package/dist/cli.js.map +1 -0
- package/dist/config-validation.d.ts +6 -0
- package/dist/config-validation.d.ts.map +1 -0
- package/dist/config-validation.js +133 -0
- package/dist/config-validation.js.map +1 -0
- package/dist/github.d.ts +11 -0
- package/dist/github.d.ts.map +1 -0
- package/dist/github.js +138 -0
- package/dist/github.js.map +1 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +119 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/anthropic.d.ts +3 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +46 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/custom.d.ts +3 -0
- package/dist/providers/custom.d.ts.map +1 -0
- package/dist/providers/custom.js +178 -0
- package/dist/providers/custom.js.map +1 -0
- package/dist/providers/index.d.ts +3 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +34 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/openai.d.ts +3 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/openai.js +45 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/reporter.d.ts +6 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +251 -0
- package/dist/reporter.js.map +1 -0
- package/dist/retry.d.ts +8 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +51 -0
- package/dist/retry.js.map +1 -0
- package/dist/runner.d.ts +16 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +203 -0
- package/dist/runner.js.map +1 -0
- package/dist/snapshot.d.ts +7 -0
- package/dist/snapshot.d.ts.map +1 -0
- package/dist/snapshot.js +146 -0
- package/dist/snapshot.js.map +1 -0
- package/dist/types.d.ts +138 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +9 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +83 -0
- package/dist/utils.js.map +1 -0
- package/package.json +82 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runAssertions = runAssertions;
|
|
4
|
+
const builtin_1 = require("./builtin");
|
|
5
|
+
const json_schema_1 = require("./json-schema");
|
|
6
|
+
const custom_1 = require("./custom");
|
|
7
|
+
async function runAssertions(output, assertions) {
|
|
8
|
+
const results = [];
|
|
9
|
+
for (const assertion of assertions) {
|
|
10
|
+
if (assertion.type === 'json-schema') {
|
|
11
|
+
results.push((0, json_schema_1.assertJsonSchema)(output, assertion.schema));
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (assertion.type === 'custom') {
|
|
15
|
+
results.push(await (0, custom_1.assertCustom)(output, assertion.name, assertion.fn));
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const handler = builtin_1.builtinAssertions[assertion.type];
|
|
19
|
+
if (!handler) {
|
|
20
|
+
results.push({
|
|
21
|
+
type: assertion.type,
|
|
22
|
+
name: assertion.type,
|
|
23
|
+
passed: false,
|
|
24
|
+
message: `Unknown assertion type: "${assertion.type}"`,
|
|
25
|
+
});
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
results.push(handler(output, assertion));
|
|
29
|
+
}
|
|
30
|
+
return results;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/assertions/index.ts"],"names":[],"mappings":";;AAKA,sCAgCC;AApCD,uCAA8C;AAC9C,+CAAiD;AACjD,qCAAwC;AAEjC,KAAK,UAAU,aAAa,CACjC,MAAc,EACd,UAA6B;IAE7B,MAAM,OAAO,GAAsB,EAAE,CAAC;IAEtC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,SAAS,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;YACrC,OAAO,CAAC,IAAI,CAAC,IAAA,8BAAgB,EAAC,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;YACzD,SAAS;QACX,CAAC;QAED,IAAI,SAAS,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAChC,OAAO,CAAC,IAAI,CAAC,MAAM,IAAA,qBAAY,EAAC,MAAM,EAAE,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;YACvE,SAAS;QACX,CAAC;QAED,MAAM,OAAO,GAAG,2BAAiB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;QAClD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,SAAS,CAAC,IAAI;gBACpB,IAAI,EAAE,SAAS,CAAC,IAAI;gBACpB,MAAM,EAAE,KAAK;gBACb,OAAO,EAAE,4BAA4B,SAAS,CAAC,IAAI,GAAG;aACvD,CAAC,CAAC;YACH,SAAS;QACX,CAAC;QAED,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,SAA+C,CAAC,CAAC,CAAC;IACjF,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-schema.d.ts","sourceRoot":"","sources":["../../src/assertions/json-schema.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE3C,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe,CA2BjG"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.assertJsonSchema = assertJsonSchema;
|
|
7
|
+
const ajv_1 = __importDefault(require("ajv"));
|
|
8
|
+
function assertJsonSchema(output, schema) {
|
|
9
|
+
let parsed;
|
|
10
|
+
try {
|
|
11
|
+
parsed = JSON.parse(output);
|
|
12
|
+
}
|
|
13
|
+
catch (e) {
|
|
14
|
+
return {
|
|
15
|
+
type: 'json-schema',
|
|
16
|
+
name: 'json-schema',
|
|
17
|
+
passed: false,
|
|
18
|
+
expected: 'valid JSON matching schema',
|
|
19
|
+
actual: `invalid JSON: ${e.message}`,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const ajv = new ajv_1.default({ allErrors: true });
|
|
23
|
+
const validate = ajv.compile(schema);
|
|
24
|
+
const valid = validate(parsed);
|
|
25
|
+
return {
|
|
26
|
+
type: 'json-schema',
|
|
27
|
+
name: 'json-schema',
|
|
28
|
+
passed: !!valid,
|
|
29
|
+
expected: 'JSON matching provided schema',
|
|
30
|
+
actual: valid
|
|
31
|
+
? 'matches schema'
|
|
32
|
+
: `schema errors: ${(validate.errors ?? []).map(e => `${e.instancePath} ${e.message}`).join('; ')}`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=json-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-schema.js","sourceRoot":"","sources":["../../src/assertions/json-schema.ts"],"names":[],"mappings":";;;;;AAGA,4CA2BC;AA9BD,8CAAsB;AAGtB,SAAgB,gBAAgB,CAAC,MAAc,EAAE,MAA+B;IAC9E,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO;YACL,IAAI,EAAE,aAAa;YACnB,IAAI,EAAE,aAAa;YACnB,MAAM,EAAE,KAAK;YACb,QAAQ,EAAE,4BAA4B;YACtC,MAAM,EAAE,iBAAkB,CAAW,CAAC,OAAO,EAAE;SAChD,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,aAAG,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzC,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IAE/B,OAAO;QACL,IAAI,EAAE,aAAa;QACnB,IAAI,EAAE,aAAa;QACnB,MAAM,EAAE,CAAC,CAAC,KAAK;QACf,QAAQ,EAAE,+BAA+B;QACzC,MAAM,EAAE,KAAK;YACX,CAAC,CAAC,gBAAgB;YAClB,CAAC,CAAC,kBAAkB,CAAC,QAAQ,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;KACtG,CAAC;AACJ,CAAC"}
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare class OutputCache {
|
|
2
|
+
private dir;
|
|
3
|
+
private memCache;
|
|
4
|
+
constructor(cacheDir?: string);
|
|
5
|
+
private cacheKey;
|
|
6
|
+
get(prompt: string, model: string): Promise<string | null>;
|
|
7
|
+
set(prompt: string, model: string, output: string): Promise<void>;
|
|
8
|
+
clear(): Promise<void>;
|
|
9
|
+
stats(): Promise<{
|
|
10
|
+
entries: number;
|
|
11
|
+
sizeBytes: number;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAaA,qBAAa,WAAW;IACtB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,QAAQ,CAA6B;gBAEjC,QAAQ,GAAE,MAA0B;IAIhD,OAAO,CAAC,QAAQ;IAIV,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAoB1D,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAejE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IActB,KAAK,IAAI,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CAiB/D"}
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.OutputCache = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const utils_1 = require("./utils");
|
|
40
|
+
const DEFAULT_CACHE_DIR = '.promptlock/cache';
|
|
41
|
+
class OutputCache {
|
|
42
|
+
dir;
|
|
43
|
+
memCache = new Map();
|
|
44
|
+
constructor(cacheDir = DEFAULT_CACHE_DIR) {
|
|
45
|
+
this.dir = cacheDir;
|
|
46
|
+
}
|
|
47
|
+
cacheKey(prompt, model) {
|
|
48
|
+
return (0, utils_1.hashString)(`${model}:${prompt}`);
|
|
49
|
+
}
|
|
50
|
+
async get(prompt, model) {
|
|
51
|
+
const key = this.cacheKey(prompt, model);
|
|
52
|
+
// Check memory first
|
|
53
|
+
if (this.memCache.has(key)) {
|
|
54
|
+
return this.memCache.get(key);
|
|
55
|
+
}
|
|
56
|
+
// Check disk
|
|
57
|
+
const filePath = path.join(this.dir, `${key}.json`);
|
|
58
|
+
try {
|
|
59
|
+
const content = await fs.promises.readFile(filePath, 'utf-8');
|
|
60
|
+
const entry = JSON.parse(content);
|
|
61
|
+
this.memCache.set(key, entry.output);
|
|
62
|
+
return entry.output;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async set(prompt, model, output) {
|
|
69
|
+
const key = this.cacheKey(prompt, model);
|
|
70
|
+
this.memCache.set(key, output);
|
|
71
|
+
const entry = {
|
|
72
|
+
promptHash: `sha256:${(0, utils_1.hashString)(prompt)}`,
|
|
73
|
+
model,
|
|
74
|
+
output,
|
|
75
|
+
cachedAt: new Date().toISOString(),
|
|
76
|
+
};
|
|
77
|
+
const filePath = path.join(this.dir, `${key}.json`);
|
|
78
|
+
await (0, utils_1.writeJsonFile)(filePath, entry);
|
|
79
|
+
}
|
|
80
|
+
async clear() {
|
|
81
|
+
this.memCache.clear();
|
|
82
|
+
try {
|
|
83
|
+
const files = await fs.promises.readdir(this.dir);
|
|
84
|
+
for (const file of files) {
|
|
85
|
+
if (file.endsWith('.json')) {
|
|
86
|
+
await fs.promises.unlink(path.join(this.dir, file));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// cache dir doesn't exist, nothing to clear
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async stats() {
|
|
95
|
+
let entries = 0;
|
|
96
|
+
let sizeBytes = 0;
|
|
97
|
+
try {
|
|
98
|
+
const files = await fs.promises.readdir(this.dir);
|
|
99
|
+
for (const file of files) {
|
|
100
|
+
if (file.endsWith('.json')) {
|
|
101
|
+
entries++;
|
|
102
|
+
const stat = await fs.promises.stat(path.join(this.dir, file));
|
|
103
|
+
sizeBytes += stat.size;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// cache dir doesn't exist
|
|
109
|
+
}
|
|
110
|
+
return { entries, sizeBytes };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
exports.OutputCache = OutputCache;
|
|
114
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
37
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
|
+
};
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
const commander_1 = require("commander");
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
44
|
+
const config_validation_1 = require("./config-validation");
|
|
45
|
+
const runner_1 = require("./runner");
|
|
46
|
+
const github_1 = require("./github");
|
|
47
|
+
const cache_1 = require("./cache");
|
|
48
|
+
const snapshot_1 = require("./snapshot");
|
|
49
|
+
const reporter_1 = require("./reporter");
|
|
50
|
+
const utils_1 = require("./utils");
|
|
51
|
+
const program = new commander_1.Command();
|
|
52
|
+
program
|
|
53
|
+
.name('prompt-lock')
|
|
54
|
+
.description('Version control and behavioral regression testing for LLM prompts')
|
|
55
|
+
.version('0.2.0');
|
|
56
|
+
// ── init ──────────────────────────────────────────────────────────────────────
|
|
57
|
+
program
|
|
58
|
+
.command('init')
|
|
59
|
+
.description('Initialize prompt-lock in the current project')
|
|
60
|
+
.action(async () => {
|
|
61
|
+
const baseDir = process.cwd();
|
|
62
|
+
const promptlockDir = path.join(baseDir, '.promptlock');
|
|
63
|
+
const promptsDir = path.join(baseDir, 'prompts');
|
|
64
|
+
// Create directories
|
|
65
|
+
await (0, utils_1.ensureDir)(path.join(promptlockDir, 'snapshots'));
|
|
66
|
+
await (0, utils_1.ensureDir)(path.join(promptlockDir, 'reports'));
|
|
67
|
+
await (0, utils_1.ensureDir)(promptsDir);
|
|
68
|
+
// Create config
|
|
69
|
+
const configPath = path.join(promptlockDir, 'config.json');
|
|
70
|
+
if (!fs.existsSync(configPath)) {
|
|
71
|
+
const config = {
|
|
72
|
+
promptsDir: './prompts',
|
|
73
|
+
snapshotDir: './.promptlock/snapshots',
|
|
74
|
+
reportDir: './.promptlock/reports',
|
|
75
|
+
defaultProvider: 'anthropic',
|
|
76
|
+
ci: {
|
|
77
|
+
failOnRegression: true,
|
|
78
|
+
reportFormat: ['json', 'html'],
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
await (0, utils_1.writeJsonFile)(configPath, config);
|
|
82
|
+
console.log(chalk_1.default.green('✅ Created .promptlock/config.json'));
|
|
83
|
+
}
|
|
84
|
+
// Create example prompt file
|
|
85
|
+
const examplePath = path.join(promptsDir, 'example.js');
|
|
86
|
+
if (!fs.existsSync(examplePath)) {
|
|
87
|
+
const exampleContent = `// Example prompt-lock definition
|
|
88
|
+
// Modify this file or create new ones in this directory
|
|
89
|
+
// You can export a single config or an array of configs
|
|
90
|
+
|
|
91
|
+
/** @type {import('prompt-lock').PromptLockConfig} */
|
|
92
|
+
module.exports = {
|
|
93
|
+
id: 'example-summarizer',
|
|
94
|
+
version: '1.0.0',
|
|
95
|
+
provider: 'anthropic', // 'openai', 'anthropic', or { type: 'custom', url: 'http://localhost:11434/api/generate' }
|
|
96
|
+
model: 'claude-sonnet-4-20250514',
|
|
97
|
+
|
|
98
|
+
prompt: \`You are a professional summarizer.
|
|
99
|
+
Summarize the following text in 2-3 bullet points.
|
|
100
|
+
Text: {{text}}\`,
|
|
101
|
+
|
|
102
|
+
defaultVars: {
|
|
103
|
+
text: 'The quick brown fox jumped over the lazy dog. This happened near a river on a sunny afternoon.',
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
// Optional: test with multiple inputs
|
|
107
|
+
// dataset: [
|
|
108
|
+
// { text: 'Input one...' },
|
|
109
|
+
// { text: 'Input two...' },
|
|
110
|
+
// ],
|
|
111
|
+
|
|
112
|
+
assertions: [
|
|
113
|
+
{ type: 'max-length', chars: 500 },
|
|
114
|
+
{ type: 'not-contains', value: 'I cannot' },
|
|
115
|
+
{ type: 'min-length', chars: 10 },
|
|
116
|
+
{ type: 'max-latency', ms: 10000 },
|
|
117
|
+
],
|
|
118
|
+
};
|
|
119
|
+
`;
|
|
120
|
+
await fs.promises.writeFile(examplePath, exampleContent, 'utf-8');
|
|
121
|
+
console.log(chalk_1.default.green('✅ Created prompts/example.js'));
|
|
122
|
+
}
|
|
123
|
+
// Update .gitignore
|
|
124
|
+
const gitignorePath = path.join(baseDir, '.gitignore');
|
|
125
|
+
const entriesToAdd = ['.promptlock/snapshots/', '.promptlock/reports/'];
|
|
126
|
+
try {
|
|
127
|
+
let gitignore = '';
|
|
128
|
+
if (fs.existsSync(gitignorePath)) {
|
|
129
|
+
gitignore = fs.readFileSync(gitignorePath, 'utf-8');
|
|
130
|
+
}
|
|
131
|
+
const missing = entriesToAdd.filter(e => !gitignore.includes(e));
|
|
132
|
+
if (missing.length > 0) {
|
|
133
|
+
const addition = '\n# prompt-lock\n' + missing.join('\n') + '\n';
|
|
134
|
+
fs.appendFileSync(gitignorePath, addition);
|
|
135
|
+
console.log(chalk_1.default.green('✅ Updated .gitignore'));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// .gitignore update is best-effort
|
|
140
|
+
}
|
|
141
|
+
console.log('');
|
|
142
|
+
console.log(chalk_1.default.bold('prompt-lock initialized!'));
|
|
143
|
+
console.log('');
|
|
144
|
+
console.log(' Created:');
|
|
145
|
+
console.log(` ${chalk_1.default.dim('.promptlock/')} — config, snapshots, reports`);
|
|
146
|
+
console.log(` ${chalk_1.default.dim('prompts/example.js')} — example prompt definition`);
|
|
147
|
+
console.log('');
|
|
148
|
+
console.log(' Next steps:');
|
|
149
|
+
console.log(` 1. Edit ${chalk_1.default.bold('prompts/example.js')} with your prompt and assertions`);
|
|
150
|
+
console.log(` 2. Set your API key: ${chalk_1.default.bold('export ANTHROPIC_API_KEY=...')}`);
|
|
151
|
+
console.log(` 3. Run: ${chalk_1.default.bold('prompt-lock run')}`);
|
|
152
|
+
console.log(` 4. Save baseline: ${chalk_1.default.bold('prompt-lock snapshot')}`);
|
|
153
|
+
});
|
|
154
|
+
// ── run ───────────────────────────────────────────────────────────────────────
|
|
155
|
+
program
|
|
156
|
+
.command('run')
|
|
157
|
+
.description('Run assertions against all registered prompts')
|
|
158
|
+
.option('--id <id>', 'Run only a specific prompt by ID')
|
|
159
|
+
.option('--ci', 'CI mode: exit 1 on any failure')
|
|
160
|
+
.option('--report <format>', 'Generate report: json, html, or both')
|
|
161
|
+
.option('--config <path>', 'Path to config file')
|
|
162
|
+
.option('--dry-run', 'Validate configs and show what would be tested without calling LLMs')
|
|
163
|
+
.option('--verbose', 'Show detailed output for each prompt run')
|
|
164
|
+
.option('--parallel', 'Run prompts in parallel')
|
|
165
|
+
.option('--concurrency <n>', 'Max concurrent runs (default: 5)', parseInt)
|
|
166
|
+
.option('--cache', 'Cache LLM outputs (skip calls when prompt+model unchanged)')
|
|
167
|
+
.option('--no-cache', 'Disable output caching')
|
|
168
|
+
.option('--github-pr <pr>', 'Post results as a GitHub PR comment (e.g. owner/repo#123)')
|
|
169
|
+
.action(async (opts) => {
|
|
170
|
+
const configs = await loadPromptConfigs(opts.config, opts.id);
|
|
171
|
+
if (configs.length === 0) {
|
|
172
|
+
console.log(chalk_1.default.yellow('No prompt configurations found.'));
|
|
173
|
+
console.log(chalk_1.default.dim('Run "prompt-lock init" to get started, or create files in ./prompts/'));
|
|
174
|
+
process.exitCode = 1;
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (opts.dryRun) {
|
|
178
|
+
console.log(chalk_1.default.dim(`[dry-run] Would test ${configs.length} prompt${configs.length !== 1 ? 's' : ''}:`));
|
|
179
|
+
for (const c of configs) {
|
|
180
|
+
const dsCount = c.dataset?.length ?? 0;
|
|
181
|
+
const prov = typeof c.provider === 'string' ? c.provider : `custom:${c.provider.url}`;
|
|
182
|
+
console.log(` ${chalk_1.default.bold(c.id)} — ${prov}/${c.model} — ${c.assertions.length} assertions${dsCount > 0 ? ` — ${dsCount} dataset inputs` : ''}`);
|
|
183
|
+
}
|
|
184
|
+
console.log('');
|
|
185
|
+
console.log(chalk_1.default.dim('No LLM calls were made. Remove --dry-run to execute.'));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const projectConfig = loadProjectConfig(opts.config);
|
|
189
|
+
const cacheDir = projectConfig?.snapshotDir
|
|
190
|
+
? path.join(path.dirname(projectConfig.snapshotDir), 'cache')
|
|
191
|
+
: '.promptlock/cache';
|
|
192
|
+
const runOpts = {
|
|
193
|
+
verbose: opts.verbose,
|
|
194
|
+
parallel: opts.parallel,
|
|
195
|
+
concurrency: opts.concurrency,
|
|
196
|
+
cache: opts.cache ?? false,
|
|
197
|
+
cacheDir,
|
|
198
|
+
retry: { maxRetries: 3 },
|
|
199
|
+
};
|
|
200
|
+
const spin = (0, utils_1.spinner)(`Running ${configs.length} prompt${configs.length !== 1 ? 's' : ''}...`);
|
|
201
|
+
const results = await (0, runner_1.runAll)(configs, runOpts);
|
|
202
|
+
spin.stop(`Ran ${configs.length} prompt${configs.length !== 1 ? 's' : ''}.`);
|
|
203
|
+
(0, reporter_1.printConsoleReport)(results);
|
|
204
|
+
// Show cache stats if caching was enabled
|
|
205
|
+
if (runOpts.cache) {
|
|
206
|
+
const cache = new cache_1.OutputCache(cacheDir);
|
|
207
|
+
const stats = await cache.stats();
|
|
208
|
+
if (stats.entries > 0) {
|
|
209
|
+
console.log(chalk_1.default.dim(`Cache: ${stats.entries} entries (${(stats.sizeBytes / 1024).toFixed(1)}KB)`));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// Generate reports
|
|
213
|
+
const reportFormats = parseReportFormats(opts.report, projectConfig);
|
|
214
|
+
const reportDir = projectConfig?.reportDir ?? '.promptlock/reports';
|
|
215
|
+
for (const format of reportFormats) {
|
|
216
|
+
if (format === 'json') {
|
|
217
|
+
const p = await (0, reporter_1.generateJsonReport)(results, reportDir);
|
|
218
|
+
console.log(chalk_1.default.dim(`Report saved: ${p}`));
|
|
219
|
+
}
|
|
220
|
+
if (format === 'html') {
|
|
221
|
+
const p = await (0, reporter_1.generateHtmlReport)(results, reportDir);
|
|
222
|
+
console.log(chalk_1.default.dim(`Report saved: ${p}`));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Post GitHub PR comment
|
|
226
|
+
if (opts.githubPr) {
|
|
227
|
+
const ghToken = process.env.GITHUB_TOKEN;
|
|
228
|
+
if (!ghToken) {
|
|
229
|
+
console.log(chalk_1.default.yellow('⚠️ --github-pr requires GITHUB_TOKEN environment variable'));
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
const parsed = parseGitHubPR(opts.githubPr);
|
|
233
|
+
if (parsed) {
|
|
234
|
+
try {
|
|
235
|
+
await (0, github_1.postPRComment)({
|
|
236
|
+
token: ghToken,
|
|
237
|
+
owner: parsed.owner,
|
|
238
|
+
repo: parsed.repo,
|
|
239
|
+
prNumber: parsed.pr,
|
|
240
|
+
results,
|
|
241
|
+
updateExisting: true,
|
|
242
|
+
});
|
|
243
|
+
console.log(chalk_1.default.green(`✅ Posted results to ${opts.githubPr}`));
|
|
244
|
+
}
|
|
245
|
+
catch (e) {
|
|
246
|
+
console.log(chalk_1.default.red(`❌ Failed to post PR comment: ${e.message}`));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
console.log(chalk_1.default.yellow('⚠️ Invalid --github-pr format. Use: owner/repo#123'));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Exit code
|
|
255
|
+
const anyFailed = results.some(r => !r.passed);
|
|
256
|
+
if (anyFailed && (opts.ci || projectConfig?.ci?.failOnRegression)) {
|
|
257
|
+
process.exitCode = 1;
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
// ── snapshot ──────────────────────────────────────────────────────────────────
|
|
261
|
+
program
|
|
262
|
+
.command('snapshot')
|
|
263
|
+
.description('Capture and save output baseline for prompts')
|
|
264
|
+
.option('--id <id>', 'Snapshot only a specific prompt by ID')
|
|
265
|
+
.option('--config <path>', 'Path to config file')
|
|
266
|
+
.option('--verbose', 'Show detailed output')
|
|
267
|
+
.action(async (opts) => {
|
|
268
|
+
const configs = await loadPromptConfigs(opts.config, opts.id);
|
|
269
|
+
if (configs.length === 0) {
|
|
270
|
+
console.log(chalk_1.default.yellow('No prompt configurations found.'));
|
|
271
|
+
process.exitCode = 1;
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
const projectConfig = loadProjectConfig(opts.config);
|
|
275
|
+
const snapshotDir = projectConfig?.snapshotDir ?? '.promptlock/snapshots';
|
|
276
|
+
const spin = (0, utils_1.spinner)(`Running ${configs.length} prompt${configs.length !== 1 ? 's' : ''} for snapshot...`);
|
|
277
|
+
const results = await (0, runner_1.runAll)(configs, { verbose: opts.verbose });
|
|
278
|
+
spin.stop('Done.');
|
|
279
|
+
for (const result of results) {
|
|
280
|
+
const p = await (0, snapshot_1.saveSnapshot)(result, snapshotDir);
|
|
281
|
+
const icon = result.passed ? chalk_1.default.green('✅') : chalk_1.default.yellow('⚠️');
|
|
282
|
+
console.log(`${icon} Snapshot saved: ${chalk_1.default.bold(result.id)} → ${chalk_1.default.dim(p)}`);
|
|
283
|
+
}
|
|
284
|
+
console.log('');
|
|
285
|
+
console.log(chalk_1.default.green(`${results.length} snapshot${results.length !== 1 ? 's' : ''} saved.`));
|
|
286
|
+
});
|
|
287
|
+
// ── diff ──────────────────────────────────────────────────────────────────────
|
|
288
|
+
program
|
|
289
|
+
.command('diff')
|
|
290
|
+
.description('Compare current output against saved snapshots')
|
|
291
|
+
.option('--id <id>', 'Diff only a specific prompt by ID')
|
|
292
|
+
.option('--config <path>', 'Path to config file')
|
|
293
|
+
.option('--verbose', 'Show detailed output')
|
|
294
|
+
.action(async (opts) => {
|
|
295
|
+
const configs = await loadPromptConfigs(opts.config, opts.id);
|
|
296
|
+
if (configs.length === 0) {
|
|
297
|
+
console.log(chalk_1.default.yellow('No prompt configurations found.'));
|
|
298
|
+
process.exitCode = 1;
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const projectConfig = loadProjectConfig(opts.config);
|
|
302
|
+
const snapshotDir = projectConfig?.snapshotDir ?? '.promptlock/snapshots';
|
|
303
|
+
const spin = (0, utils_1.spinner)(`Running ${configs.length} prompt${configs.length !== 1 ? 's' : ''} for diff...`);
|
|
304
|
+
const results = await (0, runner_1.runAll)(configs, { verbose: opts.verbose });
|
|
305
|
+
spin.stop('Done.');
|
|
306
|
+
const diffs = [];
|
|
307
|
+
for (const result of results) {
|
|
308
|
+
const snap = await (0, snapshot_1.loadSnapshot)(result.id, snapshotDir);
|
|
309
|
+
if (!snap) {
|
|
310
|
+
console.log(chalk_1.default.yellow(`⚠️ No snapshot found for "${result.id}". Run "prompt-lock snapshot" first.`));
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
diffs.push((0, snapshot_1.diffSnapshots)(snap, result.output));
|
|
314
|
+
}
|
|
315
|
+
if (diffs.length > 0) {
|
|
316
|
+
(0, reporter_1.printDiffReport)(diffs);
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
console.log(chalk_1.default.yellow('No diffs to show. Run "prompt-lock snapshot" to create baselines.'));
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
// ── history ───────────────────────────────────────────────────────────────────
|
|
323
|
+
program
|
|
324
|
+
.command('history')
|
|
325
|
+
.description('Show snapshot history for a prompt')
|
|
326
|
+
.argument('<id>', 'Prompt ID')
|
|
327
|
+
.option('--config <path>', 'Path to config file')
|
|
328
|
+
.action(async (id, opts) => {
|
|
329
|
+
const projectConfig = loadProjectConfig(opts.config);
|
|
330
|
+
const snapshotDir = projectConfig?.snapshotDir ?? '.promptlock/snapshots';
|
|
331
|
+
const history = await (0, snapshot_1.loadSnapshotHistory)(id, snapshotDir);
|
|
332
|
+
if (history.length === 0) {
|
|
333
|
+
console.log(chalk_1.default.yellow(`No snapshot history for "${id}".`));
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
console.log(chalk_1.default.bold(`Snapshot history for "${id}" (${history.length} entries):`));
|
|
337
|
+
console.log('');
|
|
338
|
+
for (const snap of history) {
|
|
339
|
+
const passCount = snap.assertionResults.filter(a => a.passed).length;
|
|
340
|
+
const total = snap.assertionResults.length;
|
|
341
|
+
const icon = passCount === total ? '✅' : '❌';
|
|
342
|
+
console.log(` ${icon} ${chalk_1.default.dim(snap.capturedAt)} v${snap.version ?? '?'} ${snap.model} ${passCount}/${total} passed`);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
// ── cache ─────────────────────────────────────────────────────────────────────
|
|
346
|
+
program
|
|
347
|
+
.command('cache')
|
|
348
|
+
.description('Manage the output cache')
|
|
349
|
+
.argument('<action>', '"clear" or "stats"')
|
|
350
|
+
.option('--config <path>', 'Path to config file')
|
|
351
|
+
.action(async (action, opts) => {
|
|
352
|
+
const projectConfig = loadProjectConfig(opts.config);
|
|
353
|
+
const cacheDir = projectConfig?.snapshotDir
|
|
354
|
+
? path.join(path.dirname(projectConfig.snapshotDir), 'cache')
|
|
355
|
+
: '.promptlock/cache';
|
|
356
|
+
const cache = new cache_1.OutputCache(cacheDir);
|
|
357
|
+
if (action === 'clear') {
|
|
358
|
+
await cache.clear();
|
|
359
|
+
console.log(chalk_1.default.green('✅ Cache cleared.'));
|
|
360
|
+
}
|
|
361
|
+
else if (action === 'stats') {
|
|
362
|
+
const stats = await cache.stats();
|
|
363
|
+
console.log(`Cache: ${stats.entries} entries, ${(stats.sizeBytes / 1024).toFixed(1)}KB`);
|
|
364
|
+
console.log(chalk_1.default.dim(`Location: ${cacheDir}`));
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
console.log(chalk_1.default.yellow(`Unknown action "${action}". Use "clear" or "stats".`));
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
371
|
+
function loadProjectConfig(configPath) {
|
|
372
|
+
const p = configPath ?? path.join(process.cwd(), '.promptlock', 'config.json');
|
|
373
|
+
try {
|
|
374
|
+
const content = fs.readFileSync(p, 'utf-8');
|
|
375
|
+
return JSON.parse(content);
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
async function loadPromptConfigs(configPath, filterId) {
|
|
382
|
+
const projectConfig = loadProjectConfig(configPath);
|
|
383
|
+
const promptsDir = projectConfig?.promptsDir
|
|
384
|
+
? path.resolve(process.cwd(), projectConfig.promptsDir)
|
|
385
|
+
: path.join(process.cwd(), 'prompts');
|
|
386
|
+
const configs = [];
|
|
387
|
+
try {
|
|
388
|
+
const files = fs.readdirSync(promptsDir);
|
|
389
|
+
for (const file of files) {
|
|
390
|
+
if (!file.endsWith('.js') && !file.endsWith('.json'))
|
|
391
|
+
continue;
|
|
392
|
+
const filePath = path.join(promptsDir, file);
|
|
393
|
+
try {
|
|
394
|
+
const mod = require(filePath);
|
|
395
|
+
const config = mod.default ?? mod;
|
|
396
|
+
const items = Array.isArray(config) ? config : (config && config.id) ? [config] : [];
|
|
397
|
+
for (const item of items) {
|
|
398
|
+
const validation = (0, config_validation_1.validateConfig)(item);
|
|
399
|
+
if (!validation.valid) {
|
|
400
|
+
console.warn(chalk_1.default.yellow(`⚠️ ${file}: ${validation.errors.join('; ')}`));
|
|
401
|
+
}
|
|
402
|
+
configs.push(item);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
catch (e) {
|
|
406
|
+
console.error(chalk_1.default.red(`Error loading ${file}: ${e.message}`));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
// prompts directory doesn't exist
|
|
412
|
+
}
|
|
413
|
+
if (filterId) {
|
|
414
|
+
return configs.filter(c => c.id === filterId);
|
|
415
|
+
}
|
|
416
|
+
return configs;
|
|
417
|
+
}
|
|
418
|
+
function parseReportFormats(cliFormat, projectConfig) {
|
|
419
|
+
if (cliFormat) {
|
|
420
|
+
if (cliFormat === 'both')
|
|
421
|
+
return ['json', 'html'];
|
|
422
|
+
return [cliFormat];
|
|
423
|
+
}
|
|
424
|
+
return projectConfig?.ci?.reportFormat ?? [];
|
|
425
|
+
}
|
|
426
|
+
function parseGitHubPR(value) {
|
|
427
|
+
// Format: owner/repo#123
|
|
428
|
+
const match = value.match(/^([^/]+)\/([^#]+)#(\d+)$/);
|
|
429
|
+
if (!match)
|
|
430
|
+
return null;
|
|
431
|
+
return { owner: match[1], repo: match[2], pr: parseInt(match[3]) };
|
|
432
|
+
}
|
|
433
|
+
program.parse();
|
|
434
|
+
//# sourceMappingURL=cli.js.map
|