pactwright 0.0.1

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 (83) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +68 -0
  3. package/dist/adapter/claude-code.d.ts +53 -0
  4. package/dist/adapter/claude-code.js +241 -0
  5. package/dist/adapter/commands.d.ts +19 -0
  6. package/dist/adapter/commands.js +162 -0
  7. package/dist/atomic.d.ts +6 -0
  8. package/dist/atomic.js +11 -0
  9. package/dist/cli.d.ts +2 -0
  10. package/dist/cli.js +561 -0
  11. package/dist/config/config.d.ts +55 -0
  12. package/dist/config/config.js +199 -0
  13. package/dist/config/lifecycle.d.ts +34 -0
  14. package/dist/config/lifecycle.js +81 -0
  15. package/dist/config/lock.d.ts +43 -0
  16. package/dist/config/lock.js +141 -0
  17. package/dist/context.d.ts +59 -0
  18. package/dist/context.js +111 -0
  19. package/dist/errors.d.ts +21 -0
  20. package/dist/errors.js +25 -0
  21. package/dist/eval/case.d.ts +123 -0
  22. package/dist/eval/case.js +17 -0
  23. package/dist/eval/core-suite.d.ts +3 -0
  24. package/dist/eval/core-suite.js +431 -0
  25. package/dist/eval/runner.d.ts +75 -0
  26. package/dist/eval/runner.js +159 -0
  27. package/dist/eval/sandbox.d.ts +39 -0
  28. package/dist/eval/sandbox.js +143 -0
  29. package/dist/extension/manage.d.ts +65 -0
  30. package/dist/extension/manage.js +372 -0
  31. package/dist/extension/manifest.d.ts +36 -0
  32. package/dist/extension/manifest.js +164 -0
  33. package/dist/extension/resolve.d.ts +77 -0
  34. package/dist/extension/resolve.js +271 -0
  35. package/dist/graph/edge-schema.d.ts +55 -0
  36. package/dist/graph/edge-schema.js +0 -0
  37. package/dist/graph/edges.d.ts +22 -0
  38. package/dist/graph/edges.js +63 -0
  39. package/dist/graph/ids.d.ts +14 -0
  40. package/dist/graph/ids.js +38 -0
  41. package/dist/graph/lineage.d.ts +48 -0
  42. package/dist/graph/lineage.js +226 -0
  43. package/dist/graph/mutations.d.ts +108 -0
  44. package/dist/graph/mutations.js +356 -0
  45. package/dist/graph/nodes.d.ts +46 -0
  46. package/dist/graph/nodes.js +137 -0
  47. package/dist/graph/revision.d.ts +50 -0
  48. package/dist/graph/revision.js +75 -0
  49. package/dist/graph/schema.d.ts +54 -0
  50. package/dist/graph/schema.js +90 -0
  51. package/dist/index.d.ts +33 -0
  52. package/dist/index.js +33 -0
  53. package/dist/init.d.ts +47 -0
  54. package/dist/init.js +132 -0
  55. package/dist/lifecycle/engine.d.ts +75 -0
  56. package/dist/lifecycle/engine.js +146 -0
  57. package/dist/lifecycle/record.d.ts +18 -0
  58. package/dist/lifecycle/record.js +157 -0
  59. package/dist/lifecycle/run.d.ts +62 -0
  60. package/dist/lifecycle/run.js +167 -0
  61. package/dist/loader.d.ts +38 -0
  62. package/dist/loader.js +64 -0
  63. package/dist/pack/capabilities.d.ts +22 -0
  64. package/dist/pack/capabilities.js +31 -0
  65. package/dist/pack/locate.d.ts +22 -0
  66. package/dist/pack/locate.js +80 -0
  67. package/dist/pack/manifest.d.ts +34 -0
  68. package/dist/pack/manifest.js +168 -0
  69. package/dist/pack/resolve.d.ts +92 -0
  70. package/dist/pack/resolve.js +238 -0
  71. package/dist/project.d.ts +22 -0
  72. package/dist/project.js +37 -0
  73. package/dist/sync.d.ts +54 -0
  74. package/dist/sync.js +98 -0
  75. package/dist/validate.d.ts +23 -0
  76. package/dist/validate.js +32 -0
  77. package/dist/validation.d.ts +24 -0
  78. package/dist/validation.js +83 -0
  79. package/dist/version.d.ts +2 -0
  80. package/dist/version.js +8 -0
  81. package/dist/yaml.d.ts +12 -0
  82. package/dist/yaml.js +32 -0
  83. package/package.json +65 -0
package/dist/cli.js ADDED
@@ -0,0 +1,561 @@
1
+ #!/usr/bin/env node
2
+ import { formatProblem, PactwrightError } from "./errors.js";
3
+ import { lifecycleNext, lifecycleStatus, } from "./lifecycle/engine.js";
4
+ import { noExecutor, runLifecycle } from "./lifecycle/run.js";
5
+ import { recordStage } from "./lifecycle/record.js";
6
+ import { loadContext } from "./context.js";
7
+ import { loadConfig } from "./config/config.js";
8
+ import { CORE_DELIVERY_SUITE } from "./eval/core-suite.js";
9
+ import { evalPassed, runEval } from "./eval/runner.js";
10
+ import { addExtension, removeExtension, upgradeExtension, } from "./extension/manage.js";
11
+ import { initProject } from "./init.js";
12
+ import { syncProject } from "./sync.js";
13
+ import { loadProject } from "./loader.js";
14
+ import { resolvePack } from "./pack/resolve.js";
15
+ import { validateProject } from "./validate.js";
16
+ import { findProjectRoot, projectPaths } from "./project.js";
17
+ import { runtimeVersion } from "./version.js";
18
+ const HELP = `Usage: pactwright <command> [options]
19
+
20
+ Commands:
21
+ init [--json] Create the Pactwright-owned core structure
22
+ (.pactwright, specs, .claude directories) in the
23
+ current directory and resolve the lock; existing
24
+ paths are left untouched
25
+ sync [--json] Render the Pactwright-managed .claude/ adapter
26
+ surface from config + lock (deterministic; only
27
+ files carrying the Pactwright banner are written
28
+ or removed, so user-authored files are kept)
29
+ validate [--json] Validate the Delivery Graph and typed-edge store
30
+ context <node-id> [--history] [--json] Print the current core Delivery lineage of a node
31
+ lifecycle status [--intent <id>] [--json] Report stage, completed stages, gates and lineage
32
+ lifecycle next [--intent <id>] [--json] Report the next permitted core Delivery action
33
+ lifecycle run [--intent <id>] [--json] Run automatic stages until a gate, completion,
34
+ a stage failure or a validation error
35
+ lifecycle record <stage> --file <yaml> Record the content of a graph-marking stage
36
+ (capture-intent, approve-contract, write-brief,
37
+ prepare-evidence) after the runtime checks the
38
+ transition
39
+ extension add <id|package> [--json] Enable an extension (and its dependencies),
40
+ validate the capability union and update
41
+ config and lock
42
+ extension remove <id> [--json] Disable and remove an extension; blocked while
43
+ enabled extensions depend on it, and canonical
44
+ extension data is preserved
45
+ extension upgrade <id> [--json] Re-resolve an extension and update the lock
46
+ eval [--json] Run the core Delivery evaluation suite against
47
+ the selected agent pack (deterministic assertions
48
+ and semantic dimensions reported separately)
49
+
50
+ Options:
51
+ -h, --help Show this help
52
+ -v, --version Print the runtime version
53
+ `;
54
+ function parseOptions(args, allow = {}) {
55
+ let intent;
56
+ let file;
57
+ let json = false;
58
+ let history = false;
59
+ const positional = [];
60
+ for (let i = 0; i < args.length; i += 1) {
61
+ const arg = args[i];
62
+ if (arg === "--json")
63
+ json = true;
64
+ else if (arg === "--history" && allow.history === true)
65
+ history = true;
66
+ else if (arg === "--intent" && allow.intent === true) {
67
+ intent = args[i + 1];
68
+ if (intent === undefined || intent.startsWith("--"))
69
+ return "--intent needs an intent id";
70
+ i += 1;
71
+ }
72
+ else if (arg === "--file" && allow.file === true) {
73
+ file = args[i + 1];
74
+ if (file === undefined || file.startsWith("--"))
75
+ return "--file needs a path";
76
+ i += 1;
77
+ }
78
+ else if (arg.startsWith("--"))
79
+ return `unknown option "${arg}"`;
80
+ else
81
+ positional.push(arg);
82
+ }
83
+ return {
84
+ json,
85
+ history,
86
+ positional,
87
+ ...(intent === undefined ? {} : { intent }),
88
+ ...(file === undefined ? {} : { file }),
89
+ };
90
+ }
91
+ const out = (text) => void process.stdout.write(text);
92
+ const err = (text) => void process.stderr.write(text);
93
+ function printProblems(error, json) {
94
+ if (json) {
95
+ out(`${JSON.stringify({ problems: error.problems }, null, 2)}\n`);
96
+ return;
97
+ }
98
+ out("Validation problems:\n");
99
+ for (const problem of error.problems)
100
+ out(` - ${formatProblem(problem)}\n`);
101
+ }
102
+ function formatStatus(entry) {
103
+ const lines = [];
104
+ lines.push(entry.intent === undefined ? "No active lineage" : `Intent: ${entry.intent}`);
105
+ lines.push(` state: ${entry.state}`);
106
+ lines.push(` current stage: ${entry.currentStage ?? "none (core Delivery lifecycle complete or terminal)"}`);
107
+ lines.push(` completed stages: ${entry.completed.length === 0 ? "none" : entry.completed.join(", ")}`);
108
+ if (entry.blockedStage !== undefined) {
109
+ lines.push(` blocked stage: ${entry.blockedStage} (required actor: ${entry.requiredActor})`);
110
+ }
111
+ if (entry.lineage !== undefined) {
112
+ const { decision, contract, brief, evidence } = entry.lineage;
113
+ const chain = [entry.lineage.intent, decision, contract, brief, evidence]
114
+ .filter((node) => node !== undefined)
115
+ .map((node) => node.id);
116
+ lines.push(` current lineage: ${chain.join(" → ")}`);
117
+ }
118
+ return `${lines.join("\n")}\n`;
119
+ }
120
+ function formatNext(action) {
121
+ const who = action.intent === undefined ? "No active lineage" : `Intent: ${action.intent}`;
122
+ const stage = action.stage === undefined
123
+ ? "next stage: none"
124
+ : `next stage: ${action.stage} (${action.execution}${action.actor ? `, actor ${action.actor}` : ""}${action.gate ? ", human gate" : ""})`;
125
+ return `${who}\n ${stage}\n ${action.reason}\n`;
126
+ }
127
+ function formatRun(result) {
128
+ const lines = [];
129
+ lines.push(result.intent === undefined ? "No active lineage" : `Intent: ${result.intent}`);
130
+ lines.push(` executed: ${result.executed.length === 0 ? "none" : result.executed.join(", ")}`);
131
+ switch (result.stop) {
132
+ case "completed":
133
+ lines.push(" stopped: lifecycle complete or no automatic stage to run");
134
+ break;
135
+ case "human-gate":
136
+ lines.push(` stopped: human gate at ${result.stage} (required actor: ${result.requiredActor})`);
137
+ break;
138
+ case "stage-failed":
139
+ lines.push(` stopped: stage ${result.stage} failed: ${result.message}`);
140
+ break;
141
+ case "validation-error":
142
+ lines.push(` stopped: validation error: ${result.message}`);
143
+ break;
144
+ }
145
+ return `${lines.join("\n")}\n`;
146
+ }
147
+ function record(args) {
148
+ const options = parseOptions(args, { file: true });
149
+ if (typeof options === "string" ||
150
+ options.positional.length !== 1 ||
151
+ options.file === undefined) {
152
+ const why = typeof options === "string"
153
+ ? options
154
+ : options.positional.length === 0
155
+ ? "record needs a <stage>"
156
+ : options.positional.length > 1
157
+ ? `unexpected argument "${options.positional[1]}"`
158
+ : "record needs --file <yaml>";
159
+ err(`pactwright: ${why}\n\n${HELP}`);
160
+ return 1;
161
+ }
162
+ try {
163
+ const root = findProjectRoot();
164
+ const result = recordStage(root, options.positional[0], options.file);
165
+ if (options.json) {
166
+ const created = result.created.map((node) => ({ id: node.id, type: node.type }));
167
+ out(`${JSON.stringify({ stage: result.stage, created }, null, 2)}\n`);
168
+ }
169
+ else {
170
+ out(result.created.map((node) => `created ${node.type} ${node.id}\n`).join(""));
171
+ }
172
+ return 0;
173
+ }
174
+ catch (error) {
175
+ if (!(error instanceof PactwrightError))
176
+ throw error;
177
+ printProblems(error, options.json);
178
+ return 1;
179
+ }
180
+ }
181
+ async function lifecycle(sub, args) {
182
+ if (sub === "record")
183
+ return record(args);
184
+ const options = parseOptions(args, { intent: true });
185
+ if (typeof options === "string" || options.positional.length > 0) {
186
+ const why = typeof options === "string" ? options : `unexpected argument "${options.positional[0]}"`;
187
+ err(`pactwright: ${why}\n\n${HELP}`);
188
+ return 1;
189
+ }
190
+ if (sub === "run") {
191
+ // `runLifecycle` loads the project itself and reports load problems as a
192
+ // validation-error stop; only the root is resolved here.
193
+ let root;
194
+ try {
195
+ root = findProjectRoot();
196
+ }
197
+ catch (error) {
198
+ if (!(error instanceof PactwrightError))
199
+ throw error;
200
+ printProblems(error, options.json);
201
+ return 1;
202
+ }
203
+ const results = await runLifecycle({
204
+ root,
205
+ execute: noExecutor,
206
+ ...(options.intent === undefined ? {} : { intentId: options.intent }),
207
+ });
208
+ out(options.json ? `${JSON.stringify(results, null, 2)}\n` : results.map(formatRun).join(""));
209
+ return results.some((r) => r.stop === "stage-failed" || r.stop === "validation-error") ? 1 : 0;
210
+ }
211
+ if (sub !== "status" && sub !== "next") {
212
+ err(`pactwright: unknown lifecycle command "${sub ?? ""}"\n\n${HELP}`);
213
+ return 1;
214
+ }
215
+ try {
216
+ const project = loadProject();
217
+ if (sub === "status") {
218
+ const status = lifecycleStatus(project, options.intent);
219
+ out(options.json
220
+ ? `${JSON.stringify(status, null, 2)}\n`
221
+ : `${status.lineages.map(formatStatus).join("")}Validation problems: none\n`);
222
+ }
223
+ else {
224
+ const actions = lifecycleNext(project, options.intent);
225
+ out(options.json ? `${JSON.stringify(actions, null, 2)}\n` : actions.map(formatNext).join(""));
226
+ }
227
+ return 0;
228
+ }
229
+ catch (error) {
230
+ if (!(error instanceof PactwrightError))
231
+ throw error;
232
+ printProblems(error, options.json);
233
+ return 1;
234
+ }
235
+ }
236
+ function initCommand(args) {
237
+ const options = parseOptions(args);
238
+ if (typeof options === "string" || options.positional.length > 0) {
239
+ const why = typeof options === "string" ? options : `unexpected argument "${options.positional[0]}"`;
240
+ err(`pactwright: ${why}\n\n${HELP}`);
241
+ return 1;
242
+ }
243
+ // Init is the one command that must not search for an enclosing project:
244
+ // it creates the project in the current directory.
245
+ const report = initProject();
246
+ if (options.json) {
247
+ out(`${JSON.stringify(report, null, 2)}\n`);
248
+ }
249
+ else {
250
+ for (const entry of report.entries) {
251
+ out(entry.action === "created" ? `created ${entry.path}\n` : `skipped ${entry.path} (exists)\n`);
252
+ }
253
+ if (report.problems.length > 0) {
254
+ out("Validation problems:\n");
255
+ for (const problem of report.problems)
256
+ out(` - ${formatProblem(problem)}\n`);
257
+ }
258
+ }
259
+ return report.ok ? 0 : 1;
260
+ }
261
+ function formatExtensionReport(report) {
262
+ const lines = [];
263
+ for (const change of report.changes) {
264
+ const version = change.action === "upgraded" && change.previousVersion !== undefined
265
+ ? ` ${change.previousVersion} → ${change.version ?? "?"}`
266
+ : change.version !== undefined
267
+ ? ` ${change.version}`
268
+ : "";
269
+ lines.push(`${change.action} ${change.id}${version}`);
270
+ }
271
+ for (const profile of report.githubProfiles) {
272
+ lines.push(`github profile "${profile}" requires provisioning (not performed: deferred)`);
273
+ }
274
+ if (report.preserved.length > 0) {
275
+ lines.push("preserved canonical extension data (delete separately if unwanted):");
276
+ for (const path of report.preserved)
277
+ lines.push(` - ${path}`);
278
+ }
279
+ return lines.map((line) => `${line}\n`).join("");
280
+ }
281
+ const EXTENSION_OPERATIONS = {
282
+ add: addExtension,
283
+ remove: removeExtension,
284
+ upgrade: upgradeExtension,
285
+ };
286
+ function extensionOperation(sub) {
287
+ // `Object.hasOwn`, not a bare lookup: `sub` is user input, and an inherited
288
+ // member like "constructor" would otherwise pass an `=== undefined` guard
289
+ // and be called as if it were an extension operation.
290
+ return sub !== undefined && Object.hasOwn(EXTENSION_OPERATIONS, sub)
291
+ ? EXTENSION_OPERATIONS[sub]
292
+ : undefined;
293
+ }
294
+ function extensionCommand(sub, args) {
295
+ // The verb is checked before the arguments, so an unknown verb is named as
296
+ // such rather than being reported as a missing extension id.
297
+ const operation = extensionOperation(sub);
298
+ if (operation === undefined) {
299
+ err(`pactwright: unknown extension command "${sub ?? ""}"\n\n${HELP}`);
300
+ return 1;
301
+ }
302
+ const options = parseOptions(args);
303
+ if (typeof options === "string" || options.positional.length !== 1) {
304
+ const why = typeof options === "string"
305
+ ? options
306
+ : options.positional.length === 0
307
+ ? `extension ${sub} needs an extension id`
308
+ : `unexpected argument "${options.positional[1]}"`;
309
+ err(`pactwright: ${why}\n\n${HELP}`);
310
+ return 1;
311
+ }
312
+ let root;
313
+ try {
314
+ root = findProjectRoot();
315
+ }
316
+ catch (error) {
317
+ if (!(error instanceof PactwrightError))
318
+ throw error;
319
+ printProblems(error, options.json);
320
+ return 1;
321
+ }
322
+ const report = operation(root, options.positional[0]);
323
+ if (options.json) {
324
+ out(`${JSON.stringify(report, null, 2)}\n`);
325
+ }
326
+ else {
327
+ out(formatExtensionReport(report));
328
+ if (report.problems.length > 0) {
329
+ // A successful command can still carry an advisory, so the heading
330
+ // follows the outcome rather than the presence of problems.
331
+ out(report.ok ? "Notes:\n" : "Validation problems:\n");
332
+ for (const problem of report.problems)
333
+ out(` - ${formatProblem(problem)}\n`);
334
+ }
335
+ }
336
+ return report.ok ? 0 : 1;
337
+ }
338
+ function syncCommand(args) {
339
+ const options = parseOptions(args);
340
+ if (typeof options === "string" || options.positional.length > 0) {
341
+ const why = typeof options === "string" ? options : `unexpected argument "${options.positional[0]}"`;
342
+ err(`pactwright: ${why}\n\n${HELP}`);
343
+ return 1;
344
+ }
345
+ let root;
346
+ try {
347
+ root = findProjectRoot();
348
+ }
349
+ catch (error) {
350
+ if (!(error instanceof PactwrightError))
351
+ throw error;
352
+ printProblems(error, options.json);
353
+ return 1;
354
+ }
355
+ const report = syncProject(root);
356
+ if (options.json) {
357
+ out(`${JSON.stringify(report, null, 2)}\n`);
358
+ }
359
+ else {
360
+ for (const path of report.changed)
361
+ out(`wrote ${path}\n`);
362
+ for (const path of report.unchanged)
363
+ out(`unchanged ${path}\n`);
364
+ for (const path of report.removed)
365
+ out(`removed ${path}\n`);
366
+ for (const path of report.kept)
367
+ out(`kept ${path} (not pactwright-generated)\n`);
368
+ for (const path of report.conflicts)
369
+ out(`conflict ${path} (not pactwright-generated)\n`);
370
+ if (report.problems.length > 0) {
371
+ out("Validation problems:\n");
372
+ for (const problem of report.problems)
373
+ out(` - ${formatProblem(problem)}\n`);
374
+ }
375
+ }
376
+ return report.ok ? 0 : 1;
377
+ }
378
+ function validate(args) {
379
+ const options = parseOptions(args);
380
+ if (typeof options === "string" || options.positional.length > 0) {
381
+ const why = typeof options === "string" ? options : `unexpected argument "${options.positional[0]}"`;
382
+ err(`pactwright: ${why}\n\n${HELP}`);
383
+ return 1;
384
+ }
385
+ const report = validateProject();
386
+ if (options.json) {
387
+ out(`${JSON.stringify(report, null, 2)}\n`);
388
+ }
389
+ else if (report.ok) {
390
+ const s = report.summary;
391
+ out(`Valid: ${s.nodes} nodes, ${s.edges} edges, ${s.lineages} lineages (revision ${s.revision})\n`);
392
+ }
393
+ else {
394
+ out("Validation problems:\n");
395
+ for (const problem of report.problems)
396
+ out(` - ${formatProblem(problem)}\n`);
397
+ }
398
+ return report.ok ? 0 : 1;
399
+ }
400
+ function formatRecord(node, extra = []) {
401
+ const lines = [`## ${node.type} ${node.id}`, `title: ${node.title}`, `created: ${node.created}`];
402
+ if (node.type === "decision") {
403
+ lines.push(`decided_by: ${String(node.frontmatter["decided_by"])}`);
404
+ lines.push(`outcome: ${String(node.frontmatter["outcome"])}`);
405
+ }
406
+ lines.push(...extra, "", node.body, "");
407
+ return `${lines.join("\n")}\n`;
408
+ }
409
+ function formatHistory(record) {
410
+ const by = record.supersededBy.length === 0 ? "none" : record.supersededBy.join(", ");
411
+ return formatRecord(record.node, [`superseded by: ${by}`]);
412
+ }
413
+ function formatContext(context) {
414
+ const parts = [`# Delivery context for ${context.requested}`, ""];
415
+ parts.push(`intent: ${context.intent}`, `state: ${context.state}`);
416
+ if (!context.requestedIsCurrent) {
417
+ parts.push(`note: ${context.requested} is superseded; the current lineage is shown`);
418
+ }
419
+ parts.push("", ...context.lineage.map((node) => formatRecord(node)));
420
+ if (context.history !== undefined) {
421
+ parts.push("# History (superseded records)", "");
422
+ parts.push(context.history.length === 0 ? "none\n" : context.history.map(formatHistory).join(""));
423
+ }
424
+ return `${parts.join("\n")}`;
425
+ }
426
+ function formatEvalCase(entry) {
427
+ const lines = [];
428
+ const agent = entry.agent === undefined ? "" : ` (agent: ${entry.agent})`;
429
+ lines.push(`${entry.id} — ${entry.title}`);
430
+ lines.push(` capability: ${entry.capability}${agent}`);
431
+ if (entry.error !== undefined)
432
+ lines.push(` error: ${entry.error}`);
433
+ if (entry.deterministic.length > 0) {
434
+ lines.push(" deterministic:");
435
+ for (const assertion of entry.deterministic) {
436
+ lines.push(` ${assertion.passed ? "pass" : "FAIL"} ${assertion.id}: ${assertion.detail}`);
437
+ }
438
+ }
439
+ if (entry.semantic.length > 0) {
440
+ lines.push(" semantic (requires judgement; reported separately, never auto-scored):");
441
+ for (const dimension of entry.semantic) {
442
+ lines.push(dimension.judged
443
+ ? ` judged ${dimension.id}: ${dimension.verdict}${dimension.rationale === undefined ? "" : ` — ${dimension.rationale}`}`
444
+ : ` unjudged ${dimension.id}: ${dimension.reason}`);
445
+ }
446
+ }
447
+ return `${lines.join("\n")}\n`;
448
+ }
449
+ function formatEvalReport(report) {
450
+ const assertions = report.cases.flatMap((entry) => entry.deterministic);
451
+ const failed = assertions.filter((assertion) => !assertion.passed).length;
452
+ const dimensions = report.cases.flatMap((entry) => entry.semantic);
453
+ const judged = dimensions.filter((dimension) => dimension.judged).length;
454
+ const errors = report.cases.filter((entry) => entry.error !== undefined).length;
455
+ return [
456
+ `Evaluating ${report.pack.name}@${report.pack.version} (runtime ${report.runtime}, suite ${report.suite})`,
457
+ "",
458
+ ...report.cases.map(formatEvalCase),
459
+ `Deterministic assertions: ${assertions.length - failed} passed, ${failed} failed${errors === 0 ? "" : `; ${errors} case(s) not evaluated`}.`,
460
+ `Semantic dimensions: ${judged} judged, ${dimensions.length - judged} unjudged. No aggregate quality score is calculated.`,
461
+ "",
462
+ ].join("\n");
463
+ }
464
+ /**
465
+ * `pactwright eval` runs inside or outside a project: inside one it
466
+ * evaluates the configured pack; outside it evaluates the default
467
+ * `@pactwright/standard` pack, since evaluation is independent from any
468
+ * project's Delivery (Distribution §16).
469
+ */
470
+ async function evalCommand(args) {
471
+ const options = parseOptions(args);
472
+ if (typeof options === "string" || options.positional.length > 0) {
473
+ const why = typeof options === "string" ? options : `unexpected argument "${options.positional[0]}"`;
474
+ err(`pactwright: ${why}\n\n${HELP}`);
475
+ return 1;
476
+ }
477
+ let root;
478
+ let config;
479
+ try {
480
+ root = findProjectRoot();
481
+ const loaded = loadConfig(projectPaths(root).config);
482
+ if (loaded.value === undefined) {
483
+ printProblems(PactwrightError.fromProblems("invalid-config", loaded.problems), options.json);
484
+ return 1;
485
+ }
486
+ config = loaded.value;
487
+ }
488
+ catch (error) {
489
+ if (!(error instanceof PactwrightError))
490
+ throw error;
491
+ root = process.cwd();
492
+ config = {
493
+ version: 1,
494
+ agentPack: { source: "@pactwright/standard" },
495
+ adapter: { type: "claude-code" },
496
+ extensions: {},
497
+ github: { enabled: false },
498
+ };
499
+ }
500
+ const resolved = resolvePack({ root, config });
501
+ if (resolved.value === undefined) {
502
+ printProblems(PactwrightError.fromProblems("pack-unresolved", resolved.problems), options.json);
503
+ return 1;
504
+ }
505
+ const report = await runEval({ pack: resolved.value, suite: CORE_DELIVERY_SUITE });
506
+ out(options.json ? `${JSON.stringify(report, null, 2)}\n` : formatEvalReport(report));
507
+ return evalPassed(report) ? 0 : 1;
508
+ }
509
+ function contextCommand(args) {
510
+ const options = parseOptions(args, { history: true });
511
+ if (typeof options === "string" || options.positional.length !== 1) {
512
+ const why = typeof options === "string"
513
+ ? options
514
+ : options.positional.length === 0
515
+ ? "context needs a <node-id>"
516
+ : `unexpected argument "${options.positional[1]}"`;
517
+ err(`pactwright: ${why}\n\n${HELP}`);
518
+ return 1;
519
+ }
520
+ try {
521
+ const context = loadContext(loadProject(), options.positional[0], {
522
+ history: options.history,
523
+ });
524
+ out(options.json ? `${JSON.stringify(context, null, 2)}\n` : formatContext(context));
525
+ return 0;
526
+ }
527
+ catch (error) {
528
+ if (!(error instanceof PactwrightError))
529
+ throw error;
530
+ printProblems(error, options.json);
531
+ return 1;
532
+ }
533
+ }
534
+ export async function main(argv) {
535
+ const [first, ...rest] = argv;
536
+ if (first === undefined || first === "--help" || first === "-h" || first === "help") {
537
+ out(HELP);
538
+ return first === undefined ? 1 : 0;
539
+ }
540
+ if (first === "--version" || first === "-v" || first === "version") {
541
+ out(`${runtimeVersion()}\n`);
542
+ return 0;
543
+ }
544
+ if (first === "init")
545
+ return initCommand(rest);
546
+ if (first === "sync")
547
+ return syncCommand(rest);
548
+ if (first === "extension")
549
+ return extensionCommand(rest[0], rest.slice(1));
550
+ if (first === "lifecycle")
551
+ return lifecycle(rest[0], rest.slice(1));
552
+ if (first === "validate")
553
+ return validate(rest);
554
+ if (first === "context")
555
+ return contextCommand(rest);
556
+ if (first === "eval")
557
+ return evalCommand(rest);
558
+ err(`pactwright: unknown command "${first}"\n\n${HELP}`);
559
+ return 1;
560
+ }
561
+ process.exitCode = await main(process.argv.slice(2));
@@ -0,0 +1,55 @@
1
+ import type { Problem } from "../errors.js";
2
+ /** One configured extension: desired state only (Distribution §4). */
3
+ export interface ConfigExtension {
4
+ /** A disabled extension keeps its graph types registered but contributes no behaviour. */
5
+ readonly enabled: boolean;
6
+ /** Package name or project-relative path the extension resolves from. */
7
+ readonly source: string;
8
+ }
9
+ /** `.pactwright/config.yml` — desired installation state (Distribution §3). */
10
+ export interface PactwrightConfig {
11
+ readonly version: 1;
12
+ readonly agentPack: {
13
+ readonly source: string;
14
+ readonly version?: string;
15
+ };
16
+ readonly adapter: {
17
+ readonly type: "claude-code";
18
+ };
19
+ /** Extension id → desired extension state (Distribution §4). */
20
+ readonly extensions: Readonly<Record<string, ConfigExtension>>;
21
+ readonly github: {
22
+ readonly enabled: boolean;
23
+ };
24
+ }
25
+ export declare const CONFIG_VERSION = 1;
26
+ export declare const ADAPTER_TYPES: readonly ["claude-code"];
27
+ export interface ParseResult<T> {
28
+ readonly value: T | undefined;
29
+ readonly problems: readonly Problem[];
30
+ }
31
+ export declare function parseConfig(raw: unknown, path: string): ParseResult<PactwrightConfig>;
32
+ export declare function loadConfig(path: string): ParseResult<PactwrightConfig>;
33
+ /**
34
+ * Serialises a configuration in the canonical `.pactwright/config.yml`
35
+ * shape — the exact bytes `pactwright init` writes for the default state.
36
+ * This is the fallback whenever the previous file cannot be edited in place;
37
+ * it re-emits from parsed values, so comments and incidental layout are not
38
+ * carried over. `rewriteConfig` is what the commands normally use.
39
+ */
40
+ export declare function serialiseConfig(config: PactwrightConfig): string;
41
+ /**
42
+ * The previous configuration text with only its `extensions:` block replaced
43
+ * (Distribution §3). `extension add` and `remove` change nothing else, so
44
+ * every other byte — comments, key order, spacing a team chose — survives.
45
+ *
46
+ * Falls back to a full `serialiseConfig` rewrite whenever the edit cannot be
47
+ * made confidently, including when the spliced result does not parse back to
48
+ * the intended configuration. The fallback is today's behaviour, so the worst
49
+ * case is losing comments rather than corrupting the file — which matters
50
+ * because `removeExtension` never rolls its write back.
51
+ *
52
+ * Comments *inside* the extensions block are part of the replaced region and
53
+ * are not preserved.
54
+ */
55
+ export declare function rewriteConfig(previous: string, config: PactwrightConfig): string;