@crewhaus/contract-compiler 0.1.1 → 0.1.3

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 (2) hide show
  1. package/package.json +7 -12
  2. package/src/index.test.ts +134 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crewhaus/contract-compiler",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "description": "Track G / §58 — two-pass contract compilation (completeness + scope/ambiguity). Source: Meta-Engineering Harnesses (arxiv 2605.25665, §4.2).",
6
6
  "main": "src/index.ts",
@@ -12,14 +12,14 @@
12
12
  "test": "bun test src"
13
13
  },
14
14
  "dependencies": {
15
- "@crewhaus/errors": "0.1.1",
16
- "@crewhaus/specialization-registry": "0.1.1"
15
+ "@crewhaus/errors": "0.1.3",
16
+ "@crewhaus/specialization-registry": "0.1.3"
17
17
  },
18
18
  "license": "Apache-2.0",
19
19
  "author": {
20
20
  "name": "Max Meier",
21
- "email": "max@studiomax.io",
22
- "url": "https://studiomax.io"
21
+ "email": "max@crewhaus.ai",
22
+ "url": "https://crewhaus.ai"
23
23
  },
24
24
  "repository": {
25
25
  "type": "git",
@@ -31,12 +31,7 @@
31
31
  "url": "https://github.com/crewhaus/factory/issues"
32
32
  },
33
33
  "publishConfig": {
34
- "access": "restricted"
34
+ "access": "public"
35
35
  },
36
- "files": [
37
- "src",
38
- "README.md",
39
- "LICENSE",
40
- "NOTICE"
41
- ]
36
+ "files": ["src", "README.md", "LICENSE", "NOTICE"]
42
37
  }
package/src/index.test.ts CHANGED
@@ -93,6 +93,100 @@ describe("compileContract — pass 2 ambiguity", () => {
93
93
  });
94
94
  });
95
95
 
96
+ describe("compileContract — completeness inference branches", () => {
97
+ test("infers a state-transition requirement when status is implied but transitions are not", async () => {
98
+ const c = await compileContract({
99
+ // mentions "status" (matches state-rule's positive signal) but never
100
+ // says "transition" (negative signal absent) -> rule fires.
101
+ rawIssue: "Track the order status and surface invalid empty failures.",
102
+ });
103
+ const completeness = c.requirements.filter((r) => r.source === "completeness-pass");
104
+ expect(completeness.some((r) => /state transitions/i.test(r.text))).toBe(true);
105
+ });
106
+
107
+ test("does not infer a state-transition requirement when 'transition' is already present", async () => {
108
+ const c = await compileContract({
109
+ rawIssue: "Document each status transition; handle invalid empty error inputs.",
110
+ });
111
+ const completeness = c.requirements.filter((r) => r.source === "completeness-pass");
112
+ expect(completeness.some((r) => /state transitions/i.test(r.text))).toBe(false);
113
+ });
114
+
115
+ test("infers a trust-boundary requirement when secrets are mentioned without a threat model", async () => {
116
+ const c = await compileContract({
117
+ // mentions "token"/"password" but no trust-boundary/threat-model/attacker.
118
+ rawIssue: "Store the user password and issue a token. Handle invalid empty failures.",
119
+ });
120
+ const completeness = c.requirements.filter((r) => r.source === "completeness-pass");
121
+ expect(completeness.some((r) => /trust boundaries/i.test(r.text))).toBe(true);
122
+ });
123
+
124
+ test("does not infer a trust-boundary requirement when a threat model is already named", async () => {
125
+ const c = await compileContract({
126
+ rawIssue:
127
+ "Handle the auth token under an explicit threat model. Cover invalid empty error inputs.",
128
+ });
129
+ const completeness = c.requirements.filter((r) => r.source === "completeness-pass");
130
+ expect(completeness.some((r) => /trust boundaries/i.test(r.text))).toBe(false);
131
+ });
132
+ });
133
+
134
+ describe("compileContract — title + requirement extraction edge cases", () => {
135
+ test("strips a leading markdown heading marker from the title", async () => {
136
+ const c = await compileContract({ rawIssue: "# Build the widget\nbody text" });
137
+ expect(c.title).toBe("Build the widget");
138
+ });
139
+
140
+ test("truncates a long single-line title to 120 chars", async () => {
141
+ const long = "x".repeat(200);
142
+ const c = await compileContract({ rawIssue: long });
143
+ expect(c.title.length).toBe(120);
144
+ });
145
+
146
+ test("ignores bullet markers that have no body", async () => {
147
+ // "-" / "1." with no following text must not produce a requirement.
148
+ const c = await compileContract({ rawIssue: "Title line\n-\n1.\n- real item" });
149
+ const userReqs = c.requirements.filter((r) => r.source === "user");
150
+ expect(userReqs.length).toBe(1);
151
+ expect(userReqs[0]?.text).toBe("real item");
152
+ });
153
+ });
154
+
155
+ describe("ambiguityPass — global-regex statefulness regression", () => {
156
+ test("flags vague quantifiers independently across consecutive requirements", async () => {
157
+ // Regression guard: VAGUE_RE carries the /g flag and is reused across the
158
+ // requirement loop. A leaked lastIndex would cause a later requirement to
159
+ // be skipped or mis-matched. Every vague line must be flagged regardless
160
+ // of position.
161
+ const c = await compileContract({
162
+ rawIssue: [
163
+ "Title",
164
+ "- return some results",
165
+ "- handle maybe the timeout",
166
+ "- a clean requirement with no vague words",
167
+ "- perhaps several edge cases",
168
+ "- another clean concrete requirement",
169
+ "- might retry once",
170
+ ].join("\n"),
171
+ });
172
+ const userReqs = c.requirements.filter((r) => r.source === "user");
173
+ const flagged = userReqs.filter((r) => r.text.includes("[QUANTIFY]"));
174
+ // Lines containing some / maybe / perhaps+several / might -> 4 flagged.
175
+ expect(flagged.length).toBe(4);
176
+ // Each flagged requirement is recorded once in ambiguitiesResolved.
177
+ expect(c.ambiguitiesResolved.length).toBe(4);
178
+ // The two concrete requirements are passed through untouched.
179
+ expect(userReqs.some((r) => r.text === "a clean requirement with no vague words")).toBe(true);
180
+ expect(userReqs.some((r) => r.text === "another clean concrete requirement")).toBe(true);
181
+ });
182
+
183
+ test("replaces every vague occurrence within a single requirement", async () => {
184
+ const c = await compileContract({ rawIssue: "Title\n- perhaps several retries" });
185
+ const req = c.requirements.find((r) => r.source === "user");
186
+ expect(req?.text).toBe("[QUANTIFY] [QUANTIFY] retries");
187
+ });
188
+ });
189
+
96
190
  describe("renderContract", () => {
97
191
  test("emits a markdown-flavored block including invariants and ambiguities", async () => {
98
192
  const c = await compileContract({
@@ -105,4 +199,44 @@ describe("renderContract", () => {
105
199
  expect(md).toContain("idempotency-key");
106
200
  expect(md).toContain("## Ambiguities resolved");
107
201
  });
202
+
203
+ test("renders the specialization header line with confidence when matched", async () => {
204
+ const c = await compileContract({ rawIssue: "anything", forceSpecialization: "auth" });
205
+ const md = renderContract(c);
206
+ expect(md).toContain("# Specialization: auth (confidence 1.00)");
207
+ });
208
+
209
+ test("renders required vs optional invariants distinctly", async () => {
210
+ const c = await compileContract({ rawIssue: "Stripe refund and invoice charge flow" });
211
+ const md = renderContract(c);
212
+ // payments has both required (idempotency-key) and optional
213
+ // (discount-deduction-source) invariants.
214
+ expect(md).toContain("- [required] idempotency-key:");
215
+ expect(md).toContain("- [optional] discount-deduction-source:");
216
+ });
217
+
218
+ test("omits the optional sections when the contract has no invariants/oos/ambiguities", async () => {
219
+ // Plain input with explicit error+edge handling -> no specialization, no
220
+ // invariants, no completeness noise that triggers ambiguity, no out-of-scope.
221
+ const c = await compileContract({
222
+ rawIssue:
223
+ "Rename a CSS class with explicit error handling and edge case enumeration for empty + invalid inputs.",
224
+ });
225
+ const md = renderContract(c);
226
+ expect(md).not.toContain("## Invariants");
227
+ expect(md).not.toContain("## Out of scope");
228
+ expect(md).not.toContain("## Ambiguities resolved");
229
+ expect(md).not.toContain("# Specialization:");
230
+ expect(md).toContain("## Requirements");
231
+ });
232
+
233
+ test("renders an out-of-scope section when present", async () => {
234
+ const c = await compileContract({
235
+ rawIssue: "Add a feature",
236
+ refiner: async (draft) => ({ ...draft, outOfScope: ["payments integration"] }),
237
+ });
238
+ const md = renderContract(c);
239
+ expect(md).toContain("## Out of scope");
240
+ expect(md).toContain("- payments integration");
241
+ });
108
242
  });