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.
Files changed (80) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/LICENSE +21 -0
  3. package/README.md +351 -0
  4. package/dist/assertions/builtin.d.ts +5 -0
  5. package/dist/assertions/builtin.d.ts.map +1 -0
  6. package/dist/assertions/builtin.js +172 -0
  7. package/dist/assertions/builtin.js.map +1 -0
  8. package/dist/assertions/custom.d.ts +3 -0
  9. package/dist/assertions/custom.d.ts.map +1 -0
  10. package/dist/assertions/custom.js +26 -0
  11. package/dist/assertions/custom.js.map +1 -0
  12. package/dist/assertions/index.d.ts +3 -0
  13. package/dist/assertions/index.d.ts.map +1 -0
  14. package/dist/assertions/index.js +32 -0
  15. package/dist/assertions/index.js.map +1 -0
  16. package/dist/assertions/json-schema.d.ts +3 -0
  17. package/dist/assertions/json-schema.d.ts.map +1 -0
  18. package/dist/assertions/json-schema.js +35 -0
  19. package/dist/assertions/json-schema.js.map +1 -0
  20. package/dist/cache.d.ts +14 -0
  21. package/dist/cache.d.ts.map +1 -0
  22. package/dist/cache.js +114 -0
  23. package/dist/cache.js.map +1 -0
  24. package/dist/cli.d.ts +3 -0
  25. package/dist/cli.d.ts.map +1 -0
  26. package/dist/cli.js +434 -0
  27. package/dist/cli.js.map +1 -0
  28. package/dist/config-validation.d.ts +6 -0
  29. package/dist/config-validation.d.ts.map +1 -0
  30. package/dist/config-validation.js +133 -0
  31. package/dist/config-validation.js.map +1 -0
  32. package/dist/github.d.ts +11 -0
  33. package/dist/github.d.ts.map +1 -0
  34. package/dist/github.js +138 -0
  35. package/dist/github.js.map +1 -0
  36. package/dist/index.d.ts +40 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +119 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/providers/anthropic.d.ts +3 -0
  41. package/dist/providers/anthropic.d.ts.map +1 -0
  42. package/dist/providers/anthropic.js +46 -0
  43. package/dist/providers/anthropic.js.map +1 -0
  44. package/dist/providers/custom.d.ts +3 -0
  45. package/dist/providers/custom.d.ts.map +1 -0
  46. package/dist/providers/custom.js +178 -0
  47. package/dist/providers/custom.js.map +1 -0
  48. package/dist/providers/index.d.ts +3 -0
  49. package/dist/providers/index.d.ts.map +1 -0
  50. package/dist/providers/index.js +34 -0
  51. package/dist/providers/index.js.map +1 -0
  52. package/dist/providers/openai.d.ts +3 -0
  53. package/dist/providers/openai.d.ts.map +1 -0
  54. package/dist/providers/openai.js +45 -0
  55. package/dist/providers/openai.js.map +1 -0
  56. package/dist/reporter.d.ts +6 -0
  57. package/dist/reporter.d.ts.map +1 -0
  58. package/dist/reporter.js +251 -0
  59. package/dist/reporter.js.map +1 -0
  60. package/dist/retry.d.ts +8 -0
  61. package/dist/retry.d.ts.map +1 -0
  62. package/dist/retry.js +51 -0
  63. package/dist/retry.js.map +1 -0
  64. package/dist/runner.d.ts +16 -0
  65. package/dist/runner.d.ts.map +1 -0
  66. package/dist/runner.js +203 -0
  67. package/dist/runner.js.map +1 -0
  68. package/dist/snapshot.d.ts +7 -0
  69. package/dist/snapshot.d.ts.map +1 -0
  70. package/dist/snapshot.js +146 -0
  71. package/dist/snapshot.js.map +1 -0
  72. package/dist/types.d.ts +138 -0
  73. package/dist/types.d.ts.map +1 -0
  74. package/dist/types.js +4 -0
  75. package/dist/types.js.map +1 -0
  76. package/dist/utils.d.ts +9 -0
  77. package/dist/utils.d.ts.map +1 -0
  78. package/dist/utils.js +83 -0
  79. package/dist/utils.js.map +1 -0
  80. 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,3 @@
1
+ import { AssertionResult } from '../types';
2
+ export declare function assertJsonSchema(output: string, schema: Record<string, unknown>): AssertionResult;
3
+ //# sourceMappingURL=json-schema.d.ts.map
@@ -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"}
@@ -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,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -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