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
@@ -0,0 +1,46 @@
1
+ import type { Problem } from "../errors.js";
2
+ import type { ParseResult } from "../config/config.js";
3
+ import { type UnknownRecord } from "../validation.js";
4
+ /**
5
+ * A Project Graph node: Markdown with YAML frontmatter (Delivery Graph §5).
6
+ * Only the common fields are typed here; type-specific fields stay in
7
+ * `frontmatter` for the schema layer to interpret.
8
+ */
9
+ export interface GraphNode {
10
+ readonly id: string;
11
+ readonly type: string;
12
+ readonly title: string;
13
+ readonly created: string;
14
+ readonly frontmatter: Readonly<UnknownRecord>;
15
+ readonly body: string;
16
+ readonly path: string;
17
+ }
18
+ export declare const REQUIRED_NODE_FIELDS: readonly ["id", "type", "title", "created"];
19
+ /** `<type>-<slug>-<short-hash>`; type and slug are lowercase kebab tokens. */
20
+ export declare const NODE_TYPE_PATTERN: RegExp;
21
+ export declare const NODE_ID_PATTERN: RegExp;
22
+ export declare const CREATED_PATTERN: RegExp;
23
+ /**
24
+ * Checks that `id` has the shape `<type>-<slug>-<short-hash>` and that its
25
+ * type prefix equals `type`. Returns problems (empty when valid).
26
+ */
27
+ export declare function checkNodeId(id: string, type: string): string | undefined;
28
+ export declare function parseNodeFile(text: string, path: string): ParseResult<GraphNode>;
29
+ /**
30
+ * IDs never change (Delivery Graph §5) and records are superseded, not
31
+ * deleted. Compares a previous graph snapshot with a proposed one and reports
32
+ * `id-removed` for every id that no longer exists — which is exactly how a
33
+ * renamed-and-re-identified node file shows up. An in-place id edit is
34
+ * already rejected by `parseNodeFile` (`filename-mismatch`). New ids are fine.
35
+ */
36
+ export declare function checkNodeIdImmutability(previous: readonly Pick<GraphNode, "id" | "path">[], proposed: readonly Pick<GraphNode, "id" | "path">[]): readonly Problem[];
37
+ export interface NodesLoadResult {
38
+ /** Successfully parsed nodes, sorted by id. */
39
+ readonly nodes: readonly GraphNode[];
40
+ readonly problems: readonly Problem[];
41
+ }
42
+ /**
43
+ * Loads every `*.md` file directly inside `dir` as a node. A missing
44
+ * directory is a problem; an existing empty directory yields no nodes.
45
+ */
46
+ export declare function loadNodes(dir: string): NodesLoadResult;
@@ -0,0 +1,137 @@
1
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
2
+ import { basename, join } from "node:path";
3
+ import { Checker, expectRecord, expectString, requireKeys, } from "../validation.js";
4
+ import { parseYaml } from "../yaml.js";
5
+ export const REQUIRED_NODE_FIELDS = ["id", "type", "title", "created"];
6
+ /** `<type>-<slug>-<short-hash>`; type and slug are lowercase kebab tokens. */
7
+ export const NODE_TYPE_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
8
+ export const NODE_ID_PATTERN = /^([a-z][a-z0-9]*(?:-[a-z0-9]+)*)-([a-z0-9]+(?:-[a-z0-9]+)*)-([0-9a-f]{4,})$/;
9
+ export const CREATED_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
10
+ const FRONTMATTER_PATTERN = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)([\s\S]*)$/;
11
+ /**
12
+ * Checks that `id` has the shape `<type>-<slug>-<short-hash>` and that its
13
+ * type prefix equals `type`. Returns problems (empty when valid).
14
+ */
15
+ export function checkNodeId(id, type) {
16
+ const match = NODE_ID_PATTERN.exec(id);
17
+ if (match === null)
18
+ return `id "${id}" must match <type>-<slug>-<short-hash>`;
19
+ if (!id.startsWith(`${type}-`)) {
20
+ return `id "${id}" must start with the node type "${type}-"`;
21
+ }
22
+ const rest = id.slice(type.length + 1);
23
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*-[0-9a-f]{4,}$/.test(rest)) {
24
+ return `id "${id}" must be "${type}-<slug>-<short-hash>"`;
25
+ }
26
+ return undefined;
27
+ }
28
+ export function parseNodeFile(text, path) {
29
+ const c = new Checker(path);
30
+ const match = FRONTMATTER_PATTERN.exec(text);
31
+ if (match === null) {
32
+ c.fail("missing-frontmatter", "node file must start with a YAML frontmatter block delimited by ---");
33
+ return { value: undefined, problems: c.problems };
34
+ }
35
+ const yaml = parseYaml(match[1] ?? "", path);
36
+ if (yaml.problems.length > 0)
37
+ return { value: undefined, problems: yaml.problems };
38
+ const front = expectRecord(c, yaml.value, "frontmatter");
39
+ if (front === undefined)
40
+ return { value: undefined, problems: c.problems };
41
+ requireKeys(c, front, "frontmatter", REQUIRED_NODE_FIELDS);
42
+ const id = expectString(c, front["id"], "frontmatter.id");
43
+ const type = expectString(c, front["type"], "frontmatter.type");
44
+ const title = expectString(c, front["title"], "frontmatter.title");
45
+ const created = expectString(c, front["created"], "frontmatter.created");
46
+ if (type !== undefined && !NODE_TYPE_PATTERN.test(type)) {
47
+ c.fail("invalid-type", `frontmatter.type "${type}" must be a lowercase kebab-case token`);
48
+ }
49
+ if (id !== undefined && type !== undefined && NODE_TYPE_PATTERN.test(type)) {
50
+ const idProblem = checkNodeId(id, type);
51
+ if (idProblem !== undefined)
52
+ c.fail("invalid-id", idProblem);
53
+ }
54
+ if (created !== undefined && !CREATED_PATTERN.test(created)) {
55
+ c.fail("invalid-value", `frontmatter.created "${created}" must be an ISO date (YYYY-MM-DD)`);
56
+ }
57
+ if (id !== undefined && basename(path) !== `${id}.md`) {
58
+ c.fail("filename-mismatch", `node file must be named "${id}.md" to match its id`);
59
+ }
60
+ const body = (match[2] ?? "").trim();
61
+ if (body.length === 0)
62
+ c.fail("missing-body", "node must have a Markdown body after the frontmatter");
63
+ if (!c.ok ||
64
+ id === undefined ||
65
+ type === undefined ||
66
+ title === undefined ||
67
+ created === undefined) {
68
+ return { value: undefined, problems: c.problems };
69
+ }
70
+ return { value: { id, type, title, created, frontmatter: front, body, path }, problems: [] };
71
+ }
72
+ /**
73
+ * IDs never change (Delivery Graph §5) and records are superseded, not
74
+ * deleted. Compares a previous graph snapshot with a proposed one and reports
75
+ * `id-removed` for every id that no longer exists — which is exactly how a
76
+ * renamed-and-re-identified node file shows up. An in-place id edit is
77
+ * already rejected by `parseNodeFile` (`filename-mismatch`). New ids are fine.
78
+ */
79
+ export function checkNodeIdImmutability(previous, proposed) {
80
+ const current = new Set(proposed.map((node) => node.id));
81
+ return previous
82
+ .filter((node) => !current.has(node.id))
83
+ .map((node) => ({
84
+ code: "id-removed",
85
+ message: `node id "${node.id}" is missing from the proposed graph; IDs never change and nodes are superseded, not deleted`,
86
+ path: node.path,
87
+ }));
88
+ }
89
+ /**
90
+ * Loads every `*.md` file directly inside `dir` as a node. A missing
91
+ * directory is a problem; an existing empty directory yields no nodes.
92
+ */
93
+ export function loadNodes(dir) {
94
+ if (!existsSync(dir) || !statSync(dir).isDirectory()) {
95
+ return {
96
+ nodes: [],
97
+ problems: [{ code: "missing-directory", message: "nodes directory not found", path: dir }],
98
+ };
99
+ }
100
+ const problems = [];
101
+ const nodes = [];
102
+ const seen = new Map();
103
+ const files = readdirSync(dir)
104
+ .filter((name) => name.endsWith(".md"))
105
+ .sort();
106
+ for (const name of files) {
107
+ const path = join(dir, name);
108
+ let content;
109
+ try {
110
+ content = readFileSync(path, "utf8");
111
+ }
112
+ catch (error) {
113
+ // A directory named *.md, unreadable permissions, a broken symlink:
114
+ // a Problem, never a raw filesystem throw out of the loader.
115
+ const detail = error instanceof Error ? error.message : String(error);
116
+ problems.push({ code: "unreadable-file", message: `cannot read file: ${detail}`, path });
117
+ continue;
118
+ }
119
+ const parsed = parseNodeFile(content, path);
120
+ problems.push(...parsed.problems);
121
+ if (parsed.value === undefined)
122
+ continue;
123
+ const previous = seen.get(parsed.value.id);
124
+ if (previous !== undefined) {
125
+ problems.push({
126
+ code: "duplicate-id",
127
+ message: `node id "${parsed.value.id}" is already declared in ${previous}`,
128
+ path,
129
+ });
130
+ continue;
131
+ }
132
+ seen.set(parsed.value.id, path);
133
+ nodes.push(parsed.value);
134
+ }
135
+ nodes.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
136
+ return { nodes, problems };
137
+ }
@@ -0,0 +1,50 @@
1
+ import type { Edge } from "./edges.js";
2
+ import type { GraphNode } from "./nodes.js";
3
+ /**
4
+ * An extension-owned canonical graph record (Assets, Publications,
5
+ * Deployments, Observations…). Extensions hand these to `graphRevision`; the
6
+ * core never interprets `record`, it only canonicalises and hashes it.
7
+ */
8
+ export interface CanonicalRecord {
9
+ /** Extension id that owns the record. */
10
+ readonly owner: string;
11
+ /** Record kind within that extension. */
12
+ readonly kind: string;
13
+ readonly id: string;
14
+ /** JSON-like data: records, arrays, strings, numbers, booleans, null. */
15
+ readonly record: unknown;
16
+ }
17
+ /** Everything that contributes to the Project Graph revision (Delivery Graph §5). */
18
+ export interface RevisionInput {
19
+ readonly nodes: readonly GraphNode[];
20
+ readonly edges: readonly Edge[];
21
+ /** Extension canonical records; absent means none. */
22
+ readonly records?: readonly CanonicalRecord[];
23
+ }
24
+ /** Bump only when the canonical payload shape changes; every revision changes with it. */
25
+ export declare const REVISION_VERSION = 1;
26
+ /** A revision is `sha256:<64 hex>`, the same shape as lock-file hashes. */
27
+ export declare const REVISION_PATTERN: RegExp;
28
+ /**
29
+ * JSON with object keys sorted recursively, `undefined` members dropped and
30
+ * no whitespace, so equal values always serialise to equal bytes.
31
+ */
32
+ export declare function canonicalJson(value: unknown): string;
33
+ /**
34
+ * The exact bytes hashed by `graphRevision`, exposed for tests and tooling.
35
+ *
36
+ * Canonicalisation (Delivery Graph §5, "canonicalise ordering before hashing"):
37
+ * nodes reduce to `{ id, frontmatter, body }` sorted by id — the file path is
38
+ * where the record sits, not graph state, and the common fields already live
39
+ * in the frontmatter; bodies use LF line endings; edges sort by
40
+ * (source, type, target); extension records sort by (owner, kind, id).
41
+ * Config, lock, lifecycle, lineage, reports, adapter output and any other
42
+ * derived or operational state never enter the payload.
43
+ */
44
+ export declare function canonicalGraphPayload(input: RevisionInput): string;
45
+ /**
46
+ * The deterministic Project Graph revision: sha256 over the canonical
47
+ * payload. The same canonical graph state always yields the same revision;
48
+ * nothing outside it can move it.
49
+ */
50
+ export declare function graphRevision(input: RevisionInput): string;
@@ -0,0 +1,75 @@
1
+ import { createHash } from "node:crypto";
2
+ import { HASH_PATTERN } from "../config/lock.js";
3
+ import { PactwrightError } from "../errors.js";
4
+ /** Bump only when the canonical payload shape changes; every revision changes with it. */
5
+ export const REVISION_VERSION = 1;
6
+ /** A revision is `sha256:<64 hex>`, the same shape as lock-file hashes. */
7
+ export const REVISION_PATTERN = HASH_PATTERN;
8
+ function compare(a, b) {
9
+ return a < b ? -1 : a > b ? 1 : 0;
10
+ }
11
+ /**
12
+ * JSON with object keys sorted recursively, `undefined` members dropped and
13
+ * no whitespace, so equal values always serialise to equal bytes.
14
+ */
15
+ export function canonicalJson(value) {
16
+ return canonicalise(value, new Set());
17
+ }
18
+ function canonicalise(value, path) {
19
+ if (value === null || typeof value !== "object")
20
+ return JSON.stringify(value) ?? "null";
21
+ if (path.has(value)) {
22
+ throw new PactwrightError("cyclic-value", "cannot canonicalise a value that contains itself");
23
+ }
24
+ path.add(value);
25
+ try {
26
+ if (Array.isArray(value)) {
27
+ return `[${value.map((item) => canonicalise(item, path)).join(",")}]`;
28
+ }
29
+ const record = value;
30
+ const members = Object.keys(record)
31
+ .filter((key) => record[key] !== undefined)
32
+ .sort(compare)
33
+ .map((key) => `${JSON.stringify(key)}:${canonicalise(record[key], path)}`);
34
+ return `{${members.join(",")}}`;
35
+ }
36
+ finally {
37
+ path.delete(value);
38
+ }
39
+ }
40
+ /**
41
+ * The exact bytes hashed by `graphRevision`, exposed for tests and tooling.
42
+ *
43
+ * Canonicalisation (Delivery Graph §5, "canonicalise ordering before hashing"):
44
+ * nodes reduce to `{ id, frontmatter, body }` sorted by id — the file path is
45
+ * where the record sits, not graph state, and the common fields already live
46
+ * in the frontmatter; bodies use LF line endings; edges sort by
47
+ * (source, type, target); extension records sort by (owner, kind, id).
48
+ * Config, lock, lifecycle, lineage, reports, adapter output and any other
49
+ * derived or operational state never enter the payload.
50
+ */
51
+ export function canonicalGraphPayload(input) {
52
+ const nodes = [...input.nodes]
53
+ .sort((a, b) => compare(a.id, b.id))
54
+ .map((node) => ({
55
+ id: node.id,
56
+ frontmatter: node.frontmatter,
57
+ body: node.body.replace(/\r\n/g, "\n"),
58
+ }));
59
+ const edges = [...input.edges]
60
+ .sort((a, b) => compare(a.source, b.source) || compare(a.type, b.type) || compare(a.target, b.target))
61
+ .map((edge) => ({ source: edge.source, type: edge.type, target: edge.target }));
62
+ const records = [...(input.records ?? [])]
63
+ .sort((a, b) => compare(a.owner, b.owner) || compare(a.kind, b.kind) || compare(a.id, b.id))
64
+ .map((item) => ({ owner: item.owner, kind: item.kind, id: item.id, record: item.record }));
65
+ return canonicalJson({ version: REVISION_VERSION, nodes, edges, records });
66
+ }
67
+ /**
68
+ * The deterministic Project Graph revision: sha256 over the canonical
69
+ * payload. The same canonical graph state always yields the same revision;
70
+ * nothing outside it can move it.
71
+ */
72
+ export function graphRevision(input) {
73
+ const digest = createHash("sha256").update(canonicalGraphPayload(input), "utf8").digest("hex");
74
+ return `sha256:${digest}`;
75
+ }
@@ -0,0 +1,54 @@
1
+ import { type Problem } from "../errors.js";
2
+ import { Checker } from "../validation.js";
3
+ import type { GraphNode } from "./nodes.js";
4
+ /** The five durable core Delivery node types (Delivery Graph §5). */
5
+ export declare const CORE_NODE_TYPES: readonly ["intent", "decision", "contract", "brief", "evidence"];
6
+ export type CoreNodeType = (typeof CORE_NODE_TYPES)[number];
7
+ /** Allowed Decision outcomes (Delivery Graph §8). */
8
+ export declare const DECISION_OUTCOMES: readonly ["proceed", "reject", "defer"];
9
+ export type DecisionOutcome = (typeof DECISION_OUTCOMES)[number];
10
+ /** `decided_by` records the actual actor as `<kind>:<name>` (Delivery Graph §8). */
11
+ export declare const DECIDED_BY_KINDS: readonly ["human", "agent", "automation"];
12
+ export type DecidedByKind = (typeof DECIDED_BY_KINDS)[number];
13
+ export declare const DECIDED_BY_PATTERN: RegExp;
14
+ export interface DecidedBy {
15
+ readonly kind: DecidedByKind;
16
+ readonly name: string;
17
+ }
18
+ /** Splits `human:samir` into `{ kind: "human", name: "samir" }`; `undefined` when malformed. */
19
+ export declare function parseDecidedBy(value: string): DecidedBy | undefined;
20
+ /**
21
+ * A node type schema: the type-specific frontmatter fields required beyond
22
+ * the common ones, plus an optional deeper check run only when those fields
23
+ * are present.
24
+ */
25
+ export interface NodeSchema {
26
+ readonly type: string;
27
+ readonly requiredFields: readonly string[];
28
+ readonly validate?: (node: GraphNode, c: Checker) => void;
29
+ }
30
+ /** Node schemas keyed by node type. */
31
+ export type NodeSchemaRegistry = Readonly<Record<string, NodeSchema>>;
32
+ /**
33
+ * Builds a frozen registry. Throws `duplicate-node-type` when two schemas
34
+ * claim the same type; later extensions compose registries with
35
+ * `createNodeSchemaRegistry([...Object.values(CORE_NODE_SCHEMAS), ...own])`.
36
+ */
37
+ export declare function createNodeSchemaRegistry(schemas: readonly NodeSchema[]): NodeSchemaRegistry;
38
+ /** Registered node types, sorted. */
39
+ export declare function nodeTypes(registry: NodeSchemaRegistry): readonly string[];
40
+ /**
41
+ * The core Delivery schema registry: exactly the five durable node types.
42
+ * Contract alternatives (§7), Delivery execution and Review (§11) are
43
+ * transient and deliberately absent.
44
+ */
45
+ export declare const CORE_NODE_SCHEMAS: NodeSchemaRegistry;
46
+ /** Validates one parsed node against the registry (Delivery Graph §21, Nodes). */
47
+ export declare function validateNode(node: GraphNode, registry: NodeSchemaRegistry): readonly Problem[];
48
+ export declare function validateNodes(nodes: readonly GraphNode[], registry: NodeSchemaRegistry): readonly Problem[];
49
+ export interface DecisionFields {
50
+ readonly outcome: DecisionOutcome;
51
+ readonly decidedBy: DecidedBy;
52
+ }
53
+ /** Typed view of a valid decision's fields; `undefined` for any other or invalid node. */
54
+ export declare function decisionFields(node: GraphNode): DecisionFields | undefined;
@@ -0,0 +1,90 @@
1
+ import { PactwrightError } from "../errors.js";
2
+ import { Checker, requireKeys } from "../validation.js";
3
+ /** The five durable core Delivery node types (Delivery Graph §5). */
4
+ export const CORE_NODE_TYPES = ["intent", "decision", "contract", "brief", "evidence"];
5
+ /** Allowed Decision outcomes (Delivery Graph §8). */
6
+ export const DECISION_OUTCOMES = ["proceed", "reject", "defer"];
7
+ /** `decided_by` records the actual actor as `<kind>:<name>` (Delivery Graph §8). */
8
+ export const DECIDED_BY_KINDS = ["human", "agent", "automation"];
9
+ export const DECIDED_BY_PATTERN = /^(human|agent|automation):\S+$/;
10
+ /** Splits `human:samir` into `{ kind: "human", name: "samir" }`; `undefined` when malformed. */
11
+ export function parseDecidedBy(value) {
12
+ if (!DECIDED_BY_PATTERN.test(value))
13
+ return undefined;
14
+ const separator = value.indexOf(":");
15
+ return { kind: value.slice(0, separator), name: value.slice(separator + 1) };
16
+ }
17
+ /**
18
+ * Builds a frozen registry. Throws `duplicate-node-type` when two schemas
19
+ * claim the same type; later extensions compose registries with
20
+ * `createNodeSchemaRegistry([...Object.values(CORE_NODE_SCHEMAS), ...own])`.
21
+ */
22
+ export function createNodeSchemaRegistry(schemas) {
23
+ // Prototype-less, so a type like "constructor" can never resolve to an
24
+ // Object.prototype member instead of a registered schema.
25
+ const registry = Object.create(null);
26
+ for (const schema of schemas) {
27
+ if (Object.hasOwn(registry, schema.type)) {
28
+ throw new PactwrightError("duplicate-node-type", `node type "${schema.type}" is already registered`);
29
+ }
30
+ registry[schema.type] = schema;
31
+ }
32
+ return Object.freeze(registry);
33
+ }
34
+ /** Registered node types, sorted. */
35
+ export function nodeTypes(registry) {
36
+ return Object.keys(registry).sort();
37
+ }
38
+ function validateDecision(node, c) {
39
+ const outcome = node.frontmatter["outcome"];
40
+ if (outcome !== undefined && !DECISION_OUTCOMES.includes(outcome)) {
41
+ c.fail("invalid-outcome", `frontmatter.outcome must be one of: ${DECISION_OUTCOMES.join(", ")}`);
42
+ }
43
+ const decidedBy = node.frontmatter["decided_by"];
44
+ if (decidedBy !== undefined &&
45
+ (typeof decidedBy !== "string" || !DECIDED_BY_PATTERN.test(decidedBy))) {
46
+ c.fail("invalid-actor", `frontmatter.decided_by must be "<kind>:<name>" with kind one of: ${DECIDED_BY_KINDS.join(", ")}`);
47
+ }
48
+ }
49
+ /**
50
+ * The core Delivery schema registry: exactly the five durable node types.
51
+ * Contract alternatives (§7), Delivery execution and Review (§11) are
52
+ * transient and deliberately absent.
53
+ */
54
+ export const CORE_NODE_SCHEMAS = createNodeSchemaRegistry([
55
+ { type: "intent", requiredFields: [] },
56
+ { type: "decision", requiredFields: ["decided_by", "outcome"], validate: validateDecision },
57
+ { type: "contract", requiredFields: [] },
58
+ { type: "brief", requiredFields: [] },
59
+ { type: "evidence", requiredFields: [] },
60
+ ]);
61
+ /** Validates one parsed node against the registry (Delivery Graph §21, Nodes). */
62
+ export function validateNode(node, registry) {
63
+ const c = new Checker(node.path);
64
+ const schema = registry[node.type];
65
+ if (schema === undefined) {
66
+ c.fail("unknown-node-type", `node type "${node.type}" is not a registered node type (known: ${nodeTypes(registry).join(", ")})`);
67
+ return c.problems;
68
+ }
69
+ requireKeys(c, node.frontmatter, "frontmatter", schema.requiredFields);
70
+ schema.validate?.(node, c);
71
+ return c.problems;
72
+ }
73
+ export function validateNodes(nodes, registry) {
74
+ return nodes.flatMap((node) => validateNode(node, registry));
75
+ }
76
+ /** Typed view of a valid decision's fields; `undefined` for any other or invalid node. */
77
+ export function decisionFields(node) {
78
+ if (node.type !== "decision")
79
+ return undefined;
80
+ const outcome = node.frontmatter["outcome"];
81
+ const rawActor = node.frontmatter["decided_by"];
82
+ if (!DECISION_OUTCOMES.includes(outcome))
83
+ return undefined;
84
+ if (typeof rawActor !== "string")
85
+ return undefined;
86
+ const decidedBy = parseDecidedBy(rawActor);
87
+ if (decidedBy === undefined)
88
+ return undefined;
89
+ return { outcome: outcome, decidedBy };
90
+ }
@@ -0,0 +1,33 @@
1
+ export { PactwrightError, formatProblem, type Problem } from "./errors.js";
2
+ export { ADAPTER_TYPES, CONFIG_VERSION, loadConfig, parseConfig, rewriteConfig, serialiseConfig, type ConfigExtension, type PactwrightConfig, type ParseResult, } from "./config/config.js";
3
+ export { ACTORS, CORE_STAGES, DECISION_STAGE, EXECUTION_MODES, LIFECYCLE_VERSION, decisionActor, humanGates, isHumanGate, loadLifecycle, parseLifecycle, type Actor, type ExecutionMode, type LifecycleConfig, type StageConfig, type StageName, } from "./config/lifecycle.js";
4
+ export { EXTENSION_ID_PATTERN, HASH_PATTERN, loadLock, parseLock, type LockExtension, type LockFile, } from "./config/lock.js";
5
+ export { CREATED_PATTERN, NODE_ID_PATTERN, NODE_TYPE_PATTERN, REQUIRED_NODE_FIELDS, checkNodeId, checkNodeIdImmutability, loadNodes, parseNodeFile, type GraphNode, type NodesLoadResult, } from "./graph/nodes.js";
6
+ export { CORE_NODE_SCHEMAS, CORE_NODE_TYPES, DECIDED_BY_KINDS, DECIDED_BY_PATTERN, DECISION_OUTCOMES, createNodeSchemaRegistry, decisionFields, nodeTypes, parseDecidedBy, validateNode, validateNodes, type CoreNodeType, type DecidedBy, type DecidedByKind, type DecisionFields, type DecisionOutcome, type NodeSchema, type NodeSchemaRegistry, } from "./graph/schema.js";
7
+ export { CORE_EDGE_OWNER, CORE_EDGE_SCHEMAS, CORE_EDGE_TYPES, createEdgeSchemaRegistry, edgeTypes, validateEdges, type CoreEdgeType, type EdgeSchema, type EdgeSchemaRegistry, } from "./graph/edge-schema.js";
8
+ export { DELIVERY_STATES, deriveLineage, deriveLineages, isCurrent, validateLineages, type DeliveryState, type Lineage, type LineageResult, } from "./graph/lineage.js";
9
+ export { EDGE_TYPE_PATTERN, edgeKey, loadEdges, parseEdges, type Edge, type EdgesParseResult, } from "./graph/edges.js";
10
+ export { mintNodeId, slugify } from "./graph/ids.js";
11
+ export { createBrief, createEvidence, createIntent, recordDecision, type CreateBriefInput, type CreateEvidenceInput, type CreateIntentInput, type RecordDecisionInput, type RecordedDecision, } from "./graph/mutations.js";
12
+ export { REVISION_PATTERN, REVISION_VERSION, canonicalGraphPayload, canonicalJson, graphRevision, type CanonicalRecord, type RevisionInput, } from "./graph/revision.js";
13
+ export { CONFIG_FILE, EDGES_FILE, LIFECYCLE_FILE, LOCK_FILE, NODES_DIR, findProjectRoot, projectPaths, type ProjectPaths, } from "./project.js";
14
+ export { loadProject, type LoadProjectOptions, type Project } from "./loader.js";
15
+ export { CONFIG_TEMPLATE, INIT_DIRS, LIFECYCLE_TEMPLATE, initProject, initTemplates, type InitEntry, type InitReport, } from "./init.js";
16
+ export { runtimeVersion } from "./version.js";
17
+ export { GRAPH_MARKING_STAGES, TRANSIENT_STAGES, completedStages, isActive, isTransientStage, lifecycleNext, lifecycleStatus, nextActionFor, pendingStages, selectLineages, type LifecycleStatus, type LineageStatus, type NextAction, } from "./lifecycle/engine.js";
18
+ export { noExecutor, runLifecycle, type RunOptions, type RunResult, type RunStop, type StageExecutor, type StageOutcome, type StageRequest, } from "./lifecycle/run.js";
19
+ export { validateProject, type ValidationReport } from "./validate.js";
20
+ export { renderGitHubWorkflows, syncProject, type SyncReport } from "./sync.js";
21
+ export { findIntentOf, loadContext, type ContextContributor, type ContextOptions, type DeliveryContext, type ExtensionContext, type HistoryRecord, } from "./context.js";
22
+ export { CAPABILITY_PATTERN, CORE_CAPABILITIES, missingCapabilities, requiredCapabilities, type CoreCapability, } from "./pack/capabilities.js";
23
+ export { PACK_MANIFEST_FILE, SKILLS_DIR, loadPackManifest, parsePackManifest, skillPath, type PackAgent, type PackManifest, } from "./pack/manifest.js";
24
+ export { agentFor, assertPackComplete, locatePack, lockEntriesFor, resolveAndLock, resolveDesiredState, resolvePack, satisfiesRange, serialiseLock, writeLock, type DesiredState, type ResolvePackOptions, type ResolvedPack, } from "./pack/resolve.js";
25
+ export { isRecordingStage, recordStage, type RecordResult, type RecordingStage, } from "./lifecycle/record.js";
26
+ export { EXTENSION_MANIFEST_FILE, loadExtensionManifest, parseExtensionManifest, type ExtensionManifest, } from "./extension/manifest.js";
27
+ export { RESERVED_NAMESPACES, composedRegistries, enabledManifests, extensionLockEntries, extensionSchemas, resolveExtensions, type ResolveExtensionsOptions, type ResolvedExtension, } from "./extension/resolve.js";
28
+ export { addExtension, removeExtension, upgradeExtension, type ExtensionChange, type ExtensionChangeReport, } from "./extension/manage.js";
29
+ export { GENERATED_MARKER, MANAGED_DIRS, isGenerated, renderClaudeCodeAdapter, writeAdapter, type RenderedFiles, type WriteAdapterResult, } from "./adapter/claude-code.js";
30
+ export { COMMAND_TEMPLATES, templateFor, type CommandTemplate } from "./adapter/commands.js";
31
+ export { type AssertionResult, type CandidateRunner, type CandidateTask, type DeterministicAssertion, type EvalCase, type EvalSuite, type Observation, type ScriptedCandidate, type SemanticDimension, type SemanticJudge, type SemanticJudgement, type ViolationCandidate, } from "./eval/case.js";
32
+ export { evalPassed, runEval, type DeterministicResult, type EvalCaseResult, type EvalOptions, type EvalReport, type SemanticResult, } from "./eval/runner.js";
33
+ export { CORE_DELIVERY_SUITE } from "./eval/core-suite.js";
package/dist/index.js ADDED
@@ -0,0 +1,33 @@
1
+ export { PactwrightError, formatProblem } from "./errors.js";
2
+ export { ADAPTER_TYPES, CONFIG_VERSION, loadConfig, parseConfig, rewriteConfig, serialiseConfig, } from "./config/config.js";
3
+ export { ACTORS, CORE_STAGES, DECISION_STAGE, EXECUTION_MODES, LIFECYCLE_VERSION, decisionActor, humanGates, isHumanGate, loadLifecycle, parseLifecycle, } from "./config/lifecycle.js";
4
+ export { EXTENSION_ID_PATTERN, HASH_PATTERN, loadLock, parseLock, } from "./config/lock.js";
5
+ export { CREATED_PATTERN, NODE_ID_PATTERN, NODE_TYPE_PATTERN, REQUIRED_NODE_FIELDS, checkNodeId, checkNodeIdImmutability, loadNodes, parseNodeFile, } from "./graph/nodes.js";
6
+ export { CORE_NODE_SCHEMAS, CORE_NODE_TYPES, DECIDED_BY_KINDS, DECIDED_BY_PATTERN, DECISION_OUTCOMES, createNodeSchemaRegistry, decisionFields, nodeTypes, parseDecidedBy, validateNode, validateNodes, } from "./graph/schema.js";
7
+ export { CORE_EDGE_OWNER, CORE_EDGE_SCHEMAS, CORE_EDGE_TYPES, createEdgeSchemaRegistry, edgeTypes, validateEdges, } from "./graph/edge-schema.js";
8
+ export { DELIVERY_STATES, deriveLineage, deriveLineages, isCurrent, validateLineages, } from "./graph/lineage.js";
9
+ export { EDGE_TYPE_PATTERN, edgeKey, loadEdges, parseEdges, } from "./graph/edges.js";
10
+ export { mintNodeId, slugify } from "./graph/ids.js";
11
+ export { createBrief, createEvidence, createIntent, recordDecision, } from "./graph/mutations.js";
12
+ export { REVISION_PATTERN, REVISION_VERSION, canonicalGraphPayload, canonicalJson, graphRevision, } from "./graph/revision.js";
13
+ export { CONFIG_FILE, EDGES_FILE, LIFECYCLE_FILE, LOCK_FILE, NODES_DIR, findProjectRoot, projectPaths, } from "./project.js";
14
+ export { loadProject } from "./loader.js";
15
+ export { CONFIG_TEMPLATE, INIT_DIRS, LIFECYCLE_TEMPLATE, initProject, initTemplates, } from "./init.js";
16
+ export { runtimeVersion } from "./version.js";
17
+ export { GRAPH_MARKING_STAGES, TRANSIENT_STAGES, completedStages, isActive, isTransientStage, lifecycleNext, lifecycleStatus, nextActionFor, pendingStages, selectLineages, } from "./lifecycle/engine.js";
18
+ export { noExecutor, runLifecycle, } from "./lifecycle/run.js";
19
+ export { validateProject } from "./validate.js";
20
+ export { renderGitHubWorkflows, syncProject } from "./sync.js";
21
+ export { findIntentOf, loadContext, } from "./context.js";
22
+ export { CAPABILITY_PATTERN, CORE_CAPABILITIES, missingCapabilities, requiredCapabilities, } from "./pack/capabilities.js";
23
+ export { PACK_MANIFEST_FILE, SKILLS_DIR, loadPackManifest, parsePackManifest, skillPath, } from "./pack/manifest.js";
24
+ export { agentFor, assertPackComplete, locatePack, lockEntriesFor, resolveAndLock, resolveDesiredState, resolvePack, satisfiesRange, serialiseLock, writeLock, } from "./pack/resolve.js";
25
+ export { isRecordingStage, recordStage, } from "./lifecycle/record.js";
26
+ export { EXTENSION_MANIFEST_FILE, loadExtensionManifest, parseExtensionManifest, } from "./extension/manifest.js";
27
+ export { RESERVED_NAMESPACES, composedRegistries, enabledManifests, extensionLockEntries, extensionSchemas, resolveExtensions, } from "./extension/resolve.js";
28
+ export { addExtension, removeExtension, upgradeExtension, } from "./extension/manage.js";
29
+ export { GENERATED_MARKER, MANAGED_DIRS, isGenerated, renderClaudeCodeAdapter, writeAdapter, } from "./adapter/claude-code.js";
30
+ export { COMMAND_TEMPLATES, templateFor } from "./adapter/commands.js";
31
+ export {} from "./eval/case.js";
32
+ export { evalPassed, runEval, } from "./eval/runner.js";
33
+ export { CORE_DELIVERY_SUITE } from "./eval/core-suite.js";
package/dist/init.d.ts ADDED
@@ -0,0 +1,47 @@
1
+ import type { Problem } from "./errors.js";
2
+ /**
3
+ * Default `.pactwright/config.yml` (Distribution §3): the `@pactwright/standard`
4
+ * pack with Claude Code defaults. `github.enabled` stays `false` until GitHub
5
+ * provisioning exists (Distribution §§9–14); `init` creates no `.github/`
6
+ * content.
7
+ */
8
+ export declare const CONFIG_TEMPLATE: string;
9
+ /** Default `.pactwright/lifecycle.yml`: the human-gated core Delivery lifecycle (Delivery Graph §17). */
10
+ export declare const LIFECYCLE_TEMPLATE = "version: 1\n\nstages:\n capture-intent:\n execution: manual\n propose-contracts:\n execution: automatic\n approve-contract:\n execution: manual\n actor: human\n write-brief:\n execution: automatic\n deliver-brief:\n execution: automatic\n review:\n execution: automatic\n prepare-evidence:\n execution: automatic\n";
11
+ /**
12
+ * The files `init` owns, in report order: relative path → content. The lock
13
+ * is not a template — it is resolved from the on-disk configuration after
14
+ * these files exist.
15
+ */
16
+ export declare function initTemplates(): ReadonlyMap<string, string>;
17
+ /**
18
+ * Directories `init` creates empty: the Claude Code adapter surface, filled
19
+ * by `pactwright sync` (Distribution §8) — `init` never copies runtime
20
+ * scripts, agents or commands into the repository (Distribution §2).
21
+ */
22
+ export declare const INIT_DIRS: readonly string[];
23
+ /** One path `init` considered: what it is and what happened to it. */
24
+ export interface InitEntry {
25
+ /** Repository-relative path, e.g. `.pactwright/config.yml`. */
26
+ readonly path: string;
27
+ readonly kind: "file" | "dir";
28
+ /** `skipped` means the path already existed and was left untouched. */
29
+ readonly action: "created" | "skipped";
30
+ }
31
+ /** `pactwright init` result. */
32
+ export interface InitReport {
33
+ readonly ok: boolean;
34
+ readonly root: string;
35
+ /** Every path considered, in order; empty `problems` when `ok`. */
36
+ readonly entries: readonly InitEntry[];
37
+ readonly problems: readonly Problem[];
38
+ }
39
+ /**
40
+ * Initialises the Pactwright-owned core structure (Distribution §§2–3) in
41
+ * `root`: configuration, lifecycle, the empty graph, the empty adapter
42
+ * directories, then the resolved `.pactwright/lock.yml`. Existing paths are
43
+ * never read or overwritten — each is reported `skipped` — so re-running in
44
+ * an initialised repository changes nothing. Finishes by validating the
45
+ * resulting project state; never throws for expected failures.
46
+ */
47
+ export declare function initProject(root?: string): InitReport;