@plures/praxis 1.3.0 → 1.4.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 (36) hide show
  1. package/dist/node/chunk-2IUFZBH3.js +87 -0
  2. package/dist/node/{chunk-WZ6B3LZ6.js → chunk-7CSWBDFL.js} +3 -56
  3. package/dist/node/{chunk-5JQJZADT.js → chunk-7M3HV4XR.js} +3 -3
  4. package/dist/node/{chunk-PTH6MD6P.js → chunk-FWOXU4MM.js} +1 -1
  5. package/dist/node/chunk-PGVSB6NR.js +59 -0
  6. package/dist/node/cli/index.cjs +1078 -211
  7. package/dist/node/cli/index.js +21 -2
  8. package/dist/node/index.cjs +1111 -0
  9. package/dist/node/index.d.cts +499 -1
  10. package/dist/node/index.d.ts +499 -1
  11. package/dist/node/index.js +1092 -78
  12. package/dist/node/integrations/svelte.js +2 -2
  13. package/dist/node/{reverse-W7THPV45.js → reverse-YD3CWIGM.js} +3 -2
  14. package/dist/node/rules-4DAJ4Z4N.js +7 -0
  15. package/dist/node/server-SYZPDULV.js +361 -0
  16. package/dist/node/{validate-EN3M4FUR.js → validate-TQGVIG7G.js} +4 -3
  17. package/package.json +28 -2
  18. package/src/__tests__/expectations.test.ts +364 -0
  19. package/src/__tests__/factory.test.ts +426 -0
  20. package/src/__tests__/mcp-server.test.ts +310 -0
  21. package/src/__tests__/project.test.ts +396 -0
  22. package/src/cli/index.ts +28 -0
  23. package/src/expectations/expectations.ts +471 -0
  24. package/src/expectations/index.ts +29 -0
  25. package/src/expectations/types.ts +95 -0
  26. package/src/factory/factory.ts +634 -0
  27. package/src/factory/index.ts +27 -0
  28. package/src/factory/types.ts +64 -0
  29. package/src/index.ts +57 -0
  30. package/src/mcp/index.ts +33 -0
  31. package/src/mcp/server.ts +485 -0
  32. package/src/mcp/types.ts +161 -0
  33. package/src/project/index.ts +31 -0
  34. package/src/project/project.ts +423 -0
  35. package/src/project/types.ts +87 -0
  36. /package/dist/node/{chunk-R2PSBPKQ.js → chunk-TEMFJOIH.js} +0 -0
@@ -609,6 +609,8 @@ __export(src_exports, {
609
609
  ContractMissing: () => ContractMissing,
610
610
  ContractUpdated: () => ContractUpdated,
611
611
  ContractValidated: () => ContractValidated,
612
+ Expectation: () => Expectation,
613
+ ExpectationSet: () => ExpectationSet,
612
614
  FrameworkAgnosticReactiveEngine: () => ReactiveLogicEngine2,
613
615
  InMemoryPraxisDB: () => InMemoryPraxisDB,
614
616
  LogicEngine: () => LogicEngine,
@@ -631,9 +633,11 @@ __export(src_exports, {
631
633
  attachToEngine: () => attachToEngine,
632
634
  attachUnumToEngine: () => attachUnumToEngine,
633
635
  auditCompleteness: () => auditCompleteness,
636
+ branchRules: () => branchRules,
634
637
  canvasToMermaid: () => canvasToMermaid,
635
638
  canvasToSchema: () => canvasToSchema,
636
639
  canvasToYaml: () => canvasToYaml,
640
+ commitFromState: () => commitFromState,
637
641
  createBehaviorLedger: () => createBehaviorLedger,
638
642
  createCanvasEditor: () => createCanvasEditor,
639
643
  createChronicle: () => createChronicle,
@@ -659,23 +663,30 @@ __export(src_exports, {
659
663
  createUIModule: () => createUIModule,
660
664
  createUnifiedApp: () => createUnifiedApp,
661
665
  createUnumAdapter: () => createUnumAdapter,
666
+ dataRules: () => dataRules,
662
667
  defineConstraint: () => defineConstraint,
663
668
  defineContract: () => defineContract,
664
669
  defineEvent: () => defineEvent,
665
670
  defineFact: () => defineFact,
671
+ defineGate: () => defineGate,
666
672
  defineModule: () => defineModule,
667
673
  defineRule: () => defineRule,
668
674
  dirtyGuardRule: () => dirtyGuardRule,
669
675
  errorDisplayRule: () => errorDisplayRule,
676
+ expectBehavior: () => expectBehavior,
677
+ expectationGate: () => expectationGate,
670
678
  fact: () => fact,
671
679
  filterEvents: () => filterEvents,
672
680
  filterFacts: () => filterFacts,
673
681
  findEvent: () => findEvent,
674
682
  findFact: () => findFact,
683
+ formRules: () => formRules,
684
+ formatGate: () => formatGate,
675
685
  formatReport: () => formatReport,
676
686
  formatValidationReport: () => formatValidationReport,
677
687
  formatValidationReportJSON: () => formatValidationReportJSON,
678
688
  formatValidationReportSARIF: () => formatValidationReportSARIF,
689
+ formatVerificationReport: () => formatVerificationReport,
679
690
  generateDocs: () => generateDocs,
680
691
  generateId: () => generateId,
681
692
  generateTauriConfig: () => generateTauriConfig,
@@ -684,7 +695,9 @@ __export(src_exports, {
684
695
  getFactPath: () => getFactPath,
685
696
  getSchemaPath: () => getSchemaPath,
686
697
  initGateRule: () => initGateRule,
698
+ inputRules: () => inputRules,
687
699
  isContract: () => isContract,
700
+ lintGate: () => lintGate,
688
701
  loadSchema: () => loadSchema,
689
702
  loadSchemaFromFile: () => loadSchemaFromFile,
690
703
  loadSchemaFromJson: () => loadSchemaFromJson,
@@ -692,18 +705,22 @@ __export(src_exports, {
692
705
  loadingGateRule: () => loadingGateRule,
693
706
  mustBeInitializedConstraint: () => mustBeInitializedConstraint,
694
707
  navigationRequest: () => navigationRequest,
708
+ navigationRules: () => navigationRules,
695
709
  noInteractionWhileLoadingConstraint: () => noInteractionWhileLoadingConstraint,
696
710
  offlineIndicatorRule: () => offlineIndicatorRule,
697
711
  registerSchema: () => registerSchema,
698
712
  resizeEvent: () => resizeEvent,
699
713
  runTerminalCommand: () => runTerminalCommand,
700
714
  schemaToCanvas: () => schemaToCanvas,
715
+ semverContract: () => semverContract,
716
+ toastRules: () => toastRules,
701
717
  uiModule: () => uiModule,
702
718
  uiStateChanged: () => uiStateChanged,
703
719
  validateContracts: () => validateContracts,
704
720
  validateForGeneration: () => validateForGeneration,
705
721
  validateSchema: () => validateSchema,
706
722
  validateWithGuardian: () => validateWithGuardian,
723
+ verify: () => verify,
707
724
  viewportRule: () => viewportRule
708
725
  });
709
726
  module.exports = __toCommonJS(src_exports);
@@ -5099,6 +5116,1083 @@ function pct(a, b) {
5099
5116
  if (b === 0) return "100%";
5100
5117
  return Math.round(a / b * 100) + "%";
5101
5118
  }
5119
+
5120
+ // src/expectations/expectations.ts
5121
+ var Expectation = class {
5122
+ name;
5123
+ _conditions = [];
5124
+ constructor(name) {
5125
+ this.name = name;
5126
+ }
5127
+ /**
5128
+ * Declare that this behavior should ONLY occur when a condition is true.
5129
+ * If the condition is false, the behavior should NOT occur.
5130
+ */
5131
+ onlyWhen(condition) {
5132
+ this._conditions.push({ description: condition, type: "onlyWhen" });
5133
+ return this;
5134
+ }
5135
+ /**
5136
+ * Declare that this behavior should NEVER occur under a given condition.
5137
+ */
5138
+ never(condition) {
5139
+ this._conditions.push({ description: condition, type: "never" });
5140
+ return this;
5141
+ }
5142
+ /**
5143
+ * Declare that this behavior should ALWAYS have a certain property.
5144
+ */
5145
+ always(condition) {
5146
+ this._conditions.push({ description: condition, type: "always" });
5147
+ return this;
5148
+ }
5149
+ /** Get all declared conditions. */
5150
+ get conditions() {
5151
+ return this._conditions;
5152
+ }
5153
+ };
5154
+ var ExpectationSet = class {
5155
+ name;
5156
+ description;
5157
+ _expectations = [];
5158
+ constructor(options) {
5159
+ this.name = options.name;
5160
+ this.description = options.description ?? "";
5161
+ }
5162
+ /** Add an expectation to the set. */
5163
+ add(expectation) {
5164
+ this._expectations.push(expectation);
5165
+ return this;
5166
+ }
5167
+ /** Get all expectations in this set. */
5168
+ get expectations() {
5169
+ return this._expectations;
5170
+ }
5171
+ /** Number of expectations. */
5172
+ get size() {
5173
+ return this._expectations.length;
5174
+ }
5175
+ };
5176
+ function expectBehavior(name) {
5177
+ return new Expectation(name);
5178
+ }
5179
+ function verify(registry, expectations) {
5180
+ const rules = registry.getAllRules();
5181
+ const constraints = registry.getAllConstraints();
5182
+ const ruleIds = registry.getRuleIds();
5183
+ const constraintIds = registry.getConstraintIds();
5184
+ const allDescriptors = [...rules, ...constraints];
5185
+ const expectationResults = [];
5186
+ for (const exp of expectations.expectations) {
5187
+ const result = verifyExpectation(exp, allDescriptors, ruleIds, constraintIds);
5188
+ expectationResults.push(result);
5189
+ }
5190
+ const satisfied = expectationResults.filter((r) => r.status === "satisfied").length;
5191
+ const violated = expectationResults.filter((r) => r.status === "violated").length;
5192
+ const partial = expectationResults.filter((r) => r.status === "partial").length;
5193
+ const allEdgeCases = expectationResults.flatMap((r) => r.edgeCases);
5194
+ const allMitigations = expectationResults.flatMap((r) => r.mitigations);
5195
+ return {
5196
+ setName: expectations.name,
5197
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
5198
+ status: violated > 0 ? "violated" : partial > 0 ? "partial" : "satisfied",
5199
+ expectations: expectationResults,
5200
+ summary: {
5201
+ total: expectationResults.length,
5202
+ satisfied,
5203
+ violated,
5204
+ partial
5205
+ },
5206
+ allEdgeCases,
5207
+ allMitigations
5208
+ };
5209
+ }
5210
+ function verifyExpectation(expectation, descriptors, ruleIds, _constraintIds) {
5211
+ const conditionResults = [];
5212
+ const edgeCases = [];
5213
+ const mitigations = [];
5214
+ const related = findRelatedDescriptors(expectation.name, descriptors);
5215
+ for (const condition of expectation.conditions) {
5216
+ const result = verifyCondition(condition, expectation.name, related, ruleIds);
5217
+ conditionResults.push(result);
5218
+ if (result.status === "unverifiable") {
5219
+ edgeCases.push(`Cannot verify "${condition.description}" for "${expectation.name}" \u2014 no covering rule/contract found`);
5220
+ mitigations.push(`Add a rule or constraint that explicitly covers: ${condition.description}`);
5221
+ } else if (result.status === "violated") {
5222
+ edgeCases.push(`"${expectation.name}" may fire incorrectly: ${condition.description}`);
5223
+ mitigations.push(`Review rule logic for "${expectation.name}" regarding: ${condition.description}`);
5224
+ }
5225
+ }
5226
+ const satisfiedCount = conditionResults.filter((r) => r.status === "satisfied").length;
5227
+ const violatedCount = conditionResults.filter((r) => r.status === "violated").length;
5228
+ const total = conditionResults.length;
5229
+ let status;
5230
+ if (total === 0) {
5231
+ status = "satisfied";
5232
+ } else if (violatedCount > 0) {
5233
+ status = "violated";
5234
+ } else if (satisfiedCount === total) {
5235
+ status = "satisfied";
5236
+ } else {
5237
+ status = "partial";
5238
+ }
5239
+ return {
5240
+ name: expectation.name,
5241
+ status,
5242
+ conditions: conditionResults,
5243
+ edgeCases,
5244
+ mitigations
5245
+ };
5246
+ }
5247
+ function textOverlaps(a, b) {
5248
+ const stopWords = /* @__PURE__ */ new Set([
5249
+ "the",
5250
+ "a",
5251
+ "an",
5252
+ "is",
5253
+ "are",
5254
+ "was",
5255
+ "were",
5256
+ "be",
5257
+ "been",
5258
+ "being",
5259
+ "have",
5260
+ "has",
5261
+ "had",
5262
+ "do",
5263
+ "does",
5264
+ "did",
5265
+ "will",
5266
+ "would",
5267
+ "could",
5268
+ "should",
5269
+ "may",
5270
+ "might",
5271
+ "shall",
5272
+ "can",
5273
+ "to",
5274
+ "of",
5275
+ "in",
5276
+ "for",
5277
+ "on",
5278
+ "with",
5279
+ "at",
5280
+ "by",
5281
+ "from",
5282
+ "as",
5283
+ "into",
5284
+ "through",
5285
+ "during",
5286
+ "before",
5287
+ "after",
5288
+ "when",
5289
+ "that",
5290
+ "this",
5291
+ "it",
5292
+ "its",
5293
+ "and",
5294
+ "or",
5295
+ "but",
5296
+ "not",
5297
+ "no",
5298
+ "if",
5299
+ "then",
5300
+ "than",
5301
+ "so",
5302
+ "up",
5303
+ "out",
5304
+ "about",
5305
+ "just",
5306
+ "must"
5307
+ ]);
5308
+ const wordsA = a.toLowerCase().replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter((w) => w.length > 2 && !stopWords.has(w));
5309
+ const wordsB = b.toLowerCase().replace(/[^a-z0-9\s]/g, "").split(/\s+/).filter((w) => w.length > 2 && !stopWords.has(w));
5310
+ if (wordsA.length === 0 || wordsB.length === 0) return false;
5311
+ const matches = wordsA.filter(
5312
+ (wa) => wordsB.some((wb) => wa.startsWith(wb.slice(0, 4)) || wb.startsWith(wa.slice(0, 4)))
5313
+ );
5314
+ const minWords = Math.min(wordsA.length, wordsB.length);
5315
+ return matches.length >= Math.max(1, Math.ceil(minWords * 0.5));
5316
+ }
5317
+ function verifyCondition(condition, expectationName, relatedDescriptors, _ruleIds) {
5318
+ if (relatedDescriptors.length === 0) {
5319
+ return {
5320
+ condition,
5321
+ status: "unverifiable",
5322
+ explanation: `No rules or constraints found related to "${expectationName}"`,
5323
+ relatedRules: []
5324
+ };
5325
+ }
5326
+ const relatedIds = relatedDescriptors.map((d) => d.id);
5327
+ const condLower = condition.description.toLowerCase();
5328
+ const matches = (target) => target.toLowerCase().includes(condLower) || condLower.includes(target.toLowerCase()) || textOverlaps(condLower, target);
5329
+ switch (condition.type) {
5330
+ case "onlyWhen": {
5331
+ const coveringRule = relatedDescriptors.find(
5332
+ (d) => d.contract?.examples.some(
5333
+ (ex) => matches(ex.given) || matches(ex.when)
5334
+ ) || d.contract?.invariants.some((inv) => matches(inv))
5335
+ );
5336
+ if (coveringRule) {
5337
+ return {
5338
+ condition,
5339
+ status: "satisfied",
5340
+ explanation: `Rule "${coveringRule.id}" contract covers precondition: ${condition.description}`,
5341
+ relatedRules: relatedIds
5342
+ };
5343
+ }
5344
+ const descMatch = relatedDescriptors.find((d) => matches(d.description));
5345
+ if (descMatch) {
5346
+ return {
5347
+ condition,
5348
+ status: "satisfied",
5349
+ explanation: `Rule "${descMatch.id}" description addresses: ${condition.description}`,
5350
+ relatedRules: relatedIds
5351
+ };
5352
+ }
5353
+ return {
5354
+ condition,
5355
+ status: "unverifiable",
5356
+ explanation: `No rule contract explicitly covers the precondition: ${condition.description}`,
5357
+ relatedRules: relatedIds
5358
+ };
5359
+ }
5360
+ case "never": {
5361
+ const preventingDescriptor = relatedDescriptors.find(
5362
+ (d) => d.contract?.invariants.some((inv) => matches(inv)) || d.contract?.examples.some(
5363
+ (ex) => (matches(ex.given) || matches(ex.when)) && (ex.then.toLowerCase().includes("retract") || ex.then.toLowerCase().includes("fail") || ex.then.toLowerCase().includes("violation") || ex.then.toLowerCase().includes("skip") || ex.then.toLowerCase().includes("block") || ex.then.toLowerCase().includes("no "))
5364
+ ) || d.contract?.behavior.toLowerCase().includes("block") || d.contract?.behavior.toLowerCase().includes("prevent")
5365
+ );
5366
+ if (preventingDescriptor) {
5367
+ return {
5368
+ condition,
5369
+ status: "satisfied",
5370
+ explanation: `Constraint/rule "${preventingDescriptor.id}" prevents: ${condition.description}`,
5371
+ relatedRules: relatedIds
5372
+ };
5373
+ }
5374
+ return {
5375
+ condition,
5376
+ status: "unverifiable",
5377
+ explanation: `No rule or constraint explicitly prevents: ${condition.description}`,
5378
+ relatedRules: relatedIds
5379
+ };
5380
+ }
5381
+ case "always": {
5382
+ const guaranteeing = relatedDescriptors.find(
5383
+ (d) => d.contract?.invariants.some((inv) => matches(inv)) || d.contract?.behavior && matches(d.contract.behavior)
5384
+ );
5385
+ if (guaranteeing) {
5386
+ return {
5387
+ condition,
5388
+ status: "satisfied",
5389
+ explanation: `Rule "${guaranteeing.id}" guarantees: ${condition.description}`,
5390
+ relatedRules: relatedIds
5391
+ };
5392
+ }
5393
+ const exampleMatch = relatedDescriptors.find(
5394
+ (d) => d.contract?.examples.some((ex) => matches(ex.then))
5395
+ );
5396
+ if (exampleMatch) {
5397
+ return {
5398
+ condition,
5399
+ status: "satisfied",
5400
+ explanation: `Rule "${exampleMatch.id}" example demonstrates: ${condition.description}`,
5401
+ relatedRules: relatedIds
5402
+ };
5403
+ }
5404
+ return {
5405
+ condition,
5406
+ status: "unverifiable",
5407
+ explanation: `No rule contract guarantees: ${condition.description}`,
5408
+ relatedRules: relatedIds
5409
+ };
5410
+ }
5411
+ }
5412
+ }
5413
+ function findRelatedDescriptors(expectationName, descriptors) {
5414
+ const nameLower = expectationName.toLowerCase();
5415
+ const nameParts = nameLower.split(/[-_./\s]+/);
5416
+ return descriptors.filter((d) => {
5417
+ const idLower = d.id.toLowerCase();
5418
+ const descLower = d.description.toLowerCase();
5419
+ const behaviorLower = d.contract?.behavior?.toLowerCase() ?? "";
5420
+ if (idLower.includes(nameLower) || nameLower.includes(idLower)) return true;
5421
+ if (descLower.includes(nameLower)) return true;
5422
+ if (behaviorLower.includes(nameLower)) return true;
5423
+ const minParts = Math.min(2, nameParts.length);
5424
+ const matchingParts = nameParts.filter(
5425
+ (part) => part.length > 2 && (idLower.includes(part) || descLower.includes(part) || behaviorLower.includes(part))
5426
+ );
5427
+ if (matchingParts.length >= minParts) return true;
5428
+ if (d.eventTypes) {
5429
+ const eventStr = Array.isArray(d.eventTypes) ? d.eventTypes.join(" ") : d.eventTypes;
5430
+ if (eventStr.toLowerCase().includes(nameLower)) return true;
5431
+ }
5432
+ return false;
5433
+ });
5434
+ }
5435
+ function formatVerificationReport(report) {
5436
+ const lines = [];
5437
+ const icon = report.status === "satisfied" ? "\u2705" : report.status === "partial" ? "\u{1F7E1}" : "\u{1F534}";
5438
+ lines.push(`${icon} Expectations: ${report.setName} \u2014 ${report.status.toUpperCase()}`);
5439
+ lines.push(` ${report.summary.satisfied}/${report.summary.total} satisfied, ${report.summary.violated} violated, ${report.summary.partial} partial`);
5440
+ lines.push("");
5441
+ for (const exp of report.expectations) {
5442
+ const expIcon = exp.status === "satisfied" ? "\u2705" : exp.status === "partial" ? "\u{1F7E1}" : "\u{1F534}";
5443
+ lines.push(`${expIcon} ${exp.name}`);
5444
+ for (const cond of exp.conditions) {
5445
+ const condIcon = cond.status === "satisfied" ? " \u2713" : cond.status === "violated" ? " \u2717" : " ?";
5446
+ lines.push(`${condIcon} [${cond.condition.type}] ${cond.condition.description}`);
5447
+ lines.push(` ${cond.explanation}`);
5448
+ }
5449
+ if (exp.edgeCases.length > 0) {
5450
+ lines.push(" Edge cases:");
5451
+ for (const ec of exp.edgeCases) {
5452
+ lines.push(` \u26A0\uFE0F ${ec}`);
5453
+ }
5454
+ }
5455
+ lines.push("");
5456
+ }
5457
+ if (report.allMitigations.length > 0) {
5458
+ lines.push("Suggested mitigations:");
5459
+ for (const m of report.allMitigations) {
5460
+ lines.push(` \u{1F4A1} ${m}`);
5461
+ }
5462
+ }
5463
+ return lines.join("\n");
5464
+ }
5465
+
5466
+ // src/factory/factory.ts
5467
+ init_rule_result();
5468
+ var SANITIZE_PATTERNS = {
5469
+ "sql-injection": /('|"|;|--|\/\*|\*\/|xp_|exec\s|union\s+select|drop\s+table|insert\s+into|delete\s+from)/i,
5470
+ "xss": /(<script|javascript:|on\w+\s*=|<iframe|<object|<embed|<img[^>]+onerror)/i,
5471
+ "path-traversal": /(\.\.[/\\]|~\/|\/etc\/|\/proc\/)/i,
5472
+ "command-injection": /([;&|`$]|\$\(|>\s*\/)/i
5473
+ };
5474
+ function inputRules(config = {}) {
5475
+ const {
5476
+ sanitize = [],
5477
+ maxLength = 0,
5478
+ required = false,
5479
+ fieldName = "input"
5480
+ } = config;
5481
+ const rules = [];
5482
+ const constraints = [];
5483
+ if (sanitize.length > 0) {
5484
+ rules.push({
5485
+ id: `factory/input.sanitize-${fieldName}`,
5486
+ description: `Validates ${fieldName} against ${sanitize.join(", ")} patterns`,
5487
+ eventTypes: [`${fieldName}.submit`, `${fieldName}.change`],
5488
+ contract: {
5489
+ ruleId: `factory/input.sanitize-${fieldName}`,
5490
+ behavior: `Checks ${fieldName} for dangerous patterns: ${sanitize.join(", ")}`,
5491
+ examples: [
5492
+ { given: `${fieldName} contains safe text`, when: "input submitted", then: "input.valid emitted" },
5493
+ { given: `${fieldName} contains <script> tag`, when: "input submitted", then: "input.violation emitted" }
5494
+ ],
5495
+ invariants: [
5496
+ `Dangerous ${fieldName} patterns must never pass validation`,
5497
+ "All violations must include the violation type"
5498
+ ]
5499
+ },
5500
+ impl: (state2, events) => {
5501
+ const inputEvent = events.find(
5502
+ (e) => e.tag === `${fieldName}.submit` || e.tag === `${fieldName}.change`
5503
+ );
5504
+ if (!inputEvent) return RuleResult.skip("No input event");
5505
+ const value = inputEvent.payload?.value ?? state2.context.input?.value ?? "";
5506
+ const violations = [];
5507
+ for (const type of sanitize) {
5508
+ const pattern = SANITIZE_PATTERNS[type];
5509
+ if (pattern && pattern.test(value)) {
5510
+ violations.push(type);
5511
+ }
5512
+ }
5513
+ if (violations.length > 0) {
5514
+ return RuleResult.emit([
5515
+ fact(`${fieldName}.violation`, {
5516
+ field: fieldName,
5517
+ violations,
5518
+ message: `Input failed sanitization: ${violations.join(", ")}`
5519
+ })
5520
+ ]);
5521
+ }
5522
+ return RuleResult.emit([
5523
+ fact(`${fieldName}.valid`, { field: fieldName, sanitized: true })
5524
+ ]);
5525
+ }
5526
+ });
5527
+ }
5528
+ if (maxLength > 0) {
5529
+ constraints.push({
5530
+ id: `factory/input.max-length-${fieldName}`,
5531
+ description: `${fieldName} must not exceed ${maxLength} characters`,
5532
+ contract: {
5533
+ ruleId: `factory/input.max-length-${fieldName}`,
5534
+ behavior: `Enforces max length of ${maxLength} for ${fieldName}`,
5535
+ examples: [
5536
+ { given: `${fieldName} is 10 chars`, when: `maxLength is ${maxLength}`, then: maxLength >= 10 ? "passes" : "violation" }
5537
+ ],
5538
+ invariants: [`${fieldName} length must never exceed ${maxLength}`]
5539
+ },
5540
+ impl: (state2) => {
5541
+ const value = state2.context.input?.value ?? "";
5542
+ if (value.length > maxLength) {
5543
+ return `${fieldName} exceeds maximum length of ${maxLength} (got ${value.length})`;
5544
+ }
5545
+ return true;
5546
+ }
5547
+ });
5548
+ }
5549
+ if (required) {
5550
+ constraints.push({
5551
+ id: `factory/input.required-${fieldName}`,
5552
+ description: `${fieldName} is required and must not be empty`,
5553
+ contract: {
5554
+ ruleId: `factory/input.required-${fieldName}`,
5555
+ behavior: `Enforces that ${fieldName} is non-empty`,
5556
+ examples: [
5557
+ { given: `${fieldName} is "hello"`, when: "checked", then: "passes" },
5558
+ { given: `${fieldName} is empty`, when: "checked", then: "violation" }
5559
+ ],
5560
+ invariants: [`${fieldName} must never be empty when required`]
5561
+ },
5562
+ impl: (state2) => {
5563
+ const value = state2.context.input?.value ?? "";
5564
+ if (value.trim().length === 0) {
5565
+ return `${fieldName} is required but empty`;
5566
+ }
5567
+ return true;
5568
+ }
5569
+ });
5570
+ }
5571
+ return { rules, constraints };
5572
+ }
5573
+ function toastRules(config = {}) {
5574
+ const {
5575
+ requireDiff = false,
5576
+ autoDismissMs = 0,
5577
+ deduplicate = false
5578
+ } = config;
5579
+ const rules = [];
5580
+ const constraints = [];
5581
+ rules.push({
5582
+ id: "factory/toast.show",
5583
+ description: "Emits toast notification with content and config",
5584
+ eventTypes: ["toast.request"],
5585
+ contract: {
5586
+ ruleId: "factory/toast.show",
5587
+ behavior: "Shows toast when requested, respecting diff requirement and auto-dismiss",
5588
+ examples: [
5589
+ { given: "toast requested with message", when: "toast.request fires", then: "toast.show emitted" },
5590
+ ...requireDiff ? [{ given: "no diff present", when: "toast.request fires", then: "toast skipped" }] : []
5591
+ ],
5592
+ invariants: [
5593
+ "Toast message must be non-empty",
5594
+ ...requireDiff ? ["Toast must not appear when diff is empty"] : []
5595
+ ]
5596
+ },
5597
+ impl: (state2, events) => {
5598
+ const toastEvent = events.find((e) => e.tag === "toast.request");
5599
+ if (!toastEvent) return RuleResult.skip("No toast request");
5600
+ const payload = toastEvent.payload;
5601
+ const message = payload.message ?? "";
5602
+ if (!message) return RuleResult.skip("Empty toast message");
5603
+ if (requireDiff) {
5604
+ const diff = state2.context.diff;
5605
+ if (!diff || Object.keys(diff).length === 0) {
5606
+ return RuleResult.skip("No diff \u2014 toast suppressed");
5607
+ }
5608
+ }
5609
+ return RuleResult.emit([
5610
+ fact("toast.show", {
5611
+ message,
5612
+ type: payload.type ?? "info",
5613
+ autoDismissMs: autoDismissMs > 0 ? autoDismissMs : void 0,
5614
+ timestamp: Date.now()
5615
+ })
5616
+ ]);
5617
+ }
5618
+ });
5619
+ if (deduplicate) {
5620
+ constraints.push({
5621
+ id: "factory/toast.no-duplicates",
5622
+ description: "Prevents duplicate toast messages",
5623
+ contract: {
5624
+ ruleId: "factory/toast.no-duplicates",
5625
+ behavior: "Rejects toast if identical message is already showing",
5626
+ examples: [
5627
+ { given: "same toast already visible", when: "duplicate toast requested", then: "violation" }
5628
+ ],
5629
+ invariants: ["No two toasts may have the same message simultaneously"]
5630
+ },
5631
+ impl: (state2) => {
5632
+ const toasts = state2.context.toasts ?? [];
5633
+ const messages = toasts.map((t) => t.message);
5634
+ const uniqueMessages = new Set(messages);
5635
+ if (uniqueMessages.size < messages.length) {
5636
+ return "Duplicate toast detected";
5637
+ }
5638
+ return true;
5639
+ }
5640
+ });
5641
+ }
5642
+ return { rules, constraints };
5643
+ }
5644
+ function formRules(config = {}) {
5645
+ const {
5646
+ validateOnBlur = false,
5647
+ submitGate = false,
5648
+ formName = "form"
5649
+ } = config;
5650
+ const rules = [];
5651
+ const constraints = [];
5652
+ if (validateOnBlur) {
5653
+ rules.push({
5654
+ id: `factory/${formName}.validate-on-blur`,
5655
+ description: `Triggers field validation when a ${formName} field loses focus`,
5656
+ eventTypes: [`${formName}.blur`],
5657
+ contract: {
5658
+ ruleId: `factory/${formName}.validate-on-blur`,
5659
+ behavior: `Validates the blurred field and emits validation result`,
5660
+ examples: [
5661
+ { given: `${formName} field has value`, when: "field loses focus", then: "validation result emitted" }
5662
+ ],
5663
+ invariants: ["Validation must run for every blur event on a registered field"]
5664
+ },
5665
+ impl: (_state, events) => {
5666
+ const blurEvent = events.find((e) => e.tag === `${formName}.blur`);
5667
+ if (!blurEvent) return RuleResult.skip("No blur event");
5668
+ const payload = blurEvent.payload;
5669
+ const field = payload.field ?? "unknown";
5670
+ const value = payload.value;
5671
+ const valid = value !== null && value !== void 0 && value !== "";
5672
+ return RuleResult.emit([
5673
+ fact(`${formName}.field-validated`, {
5674
+ field,
5675
+ valid,
5676
+ error: valid ? null : `${field} is required`
5677
+ })
5678
+ ]);
5679
+ }
5680
+ });
5681
+ }
5682
+ if (submitGate) {
5683
+ constraints.push({
5684
+ id: `factory/${formName}.submit-gate`,
5685
+ description: `Prevents ${formName} submission when validation has not passed`,
5686
+ contract: {
5687
+ ruleId: `factory/${formName}.submit-gate`,
5688
+ behavior: `Blocks form submission until all fields are valid`,
5689
+ examples: [
5690
+ { given: `${formName} is invalid`, when: "submit attempted", then: "violation \u2014 submission blocked" },
5691
+ { given: `${formName} is valid`, when: "submit attempted", then: "passes" }
5692
+ ],
5693
+ invariants: ["Form must not submit while any field has errors"]
5694
+ },
5695
+ impl: (state2) => {
5696
+ const form = state2.context.form;
5697
+ if (!form) return true;
5698
+ if (form.submitting && !form.valid) {
5699
+ return `${formName} cannot submit: validation has not passed`;
5700
+ }
5701
+ return true;
5702
+ }
5703
+ });
5704
+ }
5705
+ rules.push({
5706
+ id: `factory/${formName}.dirty-tracking`,
5707
+ description: `Tracks whether ${formName} has unsaved changes`,
5708
+ eventTypes: [`${formName}.change`, `${formName}.reset`],
5709
+ contract: {
5710
+ ruleId: `factory/${formName}.dirty-tracking`,
5711
+ behavior: "Emits dirty state when form fields change, clears on reset",
5712
+ examples: [
5713
+ { given: "field value changed", when: "form.change fires", then: "form.dirty emitted" },
5714
+ { given: "form reset", when: "form.reset fires", then: "form.dirty retracted" }
5715
+ ],
5716
+ invariants: ["Dirty state must reflect actual field changes"]
5717
+ },
5718
+ impl: (_state, events) => {
5719
+ const resetEvent = events.find((e) => e.tag === `${formName}.reset`);
5720
+ if (resetEvent) {
5721
+ return RuleResult.retract([`${formName}.dirty`], "Form reset");
5722
+ }
5723
+ const changeEvent = events.find((e) => e.tag === `${formName}.change`);
5724
+ if (changeEvent) {
5725
+ return RuleResult.emit([
5726
+ fact(`${formName}.dirty`, { dirty: true })
5727
+ ]);
5728
+ }
5729
+ return RuleResult.skip("No form event");
5730
+ }
5731
+ });
5732
+ return { rules, constraints };
5733
+ }
5734
+ function navigationRules(config = {}) {
5735
+ const {
5736
+ dirtyGuard = false,
5737
+ authRequired = false
5738
+ } = config;
5739
+ const rules = [];
5740
+ const constraints = [];
5741
+ rules.push({
5742
+ id: "factory/navigation.handle",
5743
+ description: "Processes navigation requests and emits navigation facts",
5744
+ eventTypes: ["navigation.request"],
5745
+ contract: {
5746
+ ruleId: "factory/navigation.handle",
5747
+ behavior: "Emits navigation.allowed or navigation.blocked based on guards",
5748
+ examples: [
5749
+ { given: "no guards active", when: "navigation requested", then: "navigation.allowed emitted" },
5750
+ ...dirtyGuard ? [{ given: "form is dirty", when: "navigation requested", then: "navigation.blocked emitted" }] : [],
5751
+ ...authRequired ? [{ given: "user not authenticated", when: "navigation requested", then: "navigation.blocked emitted" }] : []
5752
+ ],
5753
+ invariants: [
5754
+ "Every navigation request must result in either allowed or blocked",
5755
+ ...dirtyGuard ? ["Navigation must be blocked when dirty data exists"] : [],
5756
+ ...authRequired ? ["Navigation must be blocked when not authenticated"] : []
5757
+ ]
5758
+ },
5759
+ impl: (state2, events) => {
5760
+ const navEvent = events.find((e) => e.tag === "navigation.request");
5761
+ if (!navEvent) return RuleResult.skip("No navigation request");
5762
+ const target = navEvent.payload?.target ?? "/";
5763
+ const reasons = [];
5764
+ if (dirtyGuard && state2.context.dirty) {
5765
+ reasons.push("Unsaved changes will be lost");
5766
+ }
5767
+ if (authRequired && !state2.context.authenticated) {
5768
+ reasons.push("Authentication required");
5769
+ }
5770
+ if (reasons.length > 0) {
5771
+ return RuleResult.emit([
5772
+ fact("navigation.blocked", { target, reasons })
5773
+ ]);
5774
+ }
5775
+ return RuleResult.emit([
5776
+ fact("navigation.allowed", { target })
5777
+ ]);
5778
+ }
5779
+ });
5780
+ if (dirtyGuard) {
5781
+ constraints.push({
5782
+ id: "factory/navigation.dirty-guard",
5783
+ description: "Prevents silent navigation when unsaved changes exist",
5784
+ contract: {
5785
+ ruleId: "factory/navigation.dirty-guard",
5786
+ behavior: "Blocks navigation when dirty state is true",
5787
+ examples: [
5788
+ { given: "dirty is true", when: "navigation attempted", then: "violation" },
5789
+ { given: "dirty is false", when: "navigation attempted", then: "passes" }
5790
+ ],
5791
+ invariants: ["Must never silently lose unsaved changes"]
5792
+ },
5793
+ impl: (state2) => {
5794
+ if (state2.context.dirty && state2.facts.some((f) => f.tag === "navigation.allowed")) {
5795
+ return "Navigation allowed while dirty \u2014 unsaved changes may be lost";
5796
+ }
5797
+ return true;
5798
+ }
5799
+ });
5800
+ }
5801
+ return { rules, constraints };
5802
+ }
5803
+ function dataRules(config = {}) {
5804
+ const {
5805
+ optimisticUpdate = false,
5806
+ rollbackOnError = false,
5807
+ cacheInvalidation = false,
5808
+ entityName = "data"
5809
+ } = config;
5810
+ const rules = [];
5811
+ const constraints = [];
5812
+ if (optimisticUpdate) {
5813
+ rules.push({
5814
+ id: `factory/${entityName}.optimistic-update`,
5815
+ description: `Applies optimistic update for ${entityName} while request is pending`,
5816
+ eventTypes: [`${entityName}.mutate`],
5817
+ contract: {
5818
+ ruleId: `factory/${entityName}.optimistic-update`,
5819
+ behavior: `Immediately emits updated ${entityName} state before server confirmation`,
5820
+ examples: [
5821
+ { given: `${entityName} mutation requested`, when: "mutate event fires", then: "optimistic state emitted" }
5822
+ ],
5823
+ invariants: [
5824
+ "Optimistic state must store original for rollback",
5825
+ "Optimistic update must be distinguishable from confirmed state"
5826
+ ]
5827
+ },
5828
+ impl: (_state, events) => {
5829
+ const mutateEvent = events.find((e) => e.tag === `${entityName}.mutate`);
5830
+ if (!mutateEvent) return RuleResult.skip("No mutation event");
5831
+ const payload = mutateEvent.payload;
5832
+ return RuleResult.emit([
5833
+ fact(`${entityName}.optimistic`, {
5834
+ id: payload.id,
5835
+ data: payload.data,
5836
+ pending: true,
5837
+ timestamp: Date.now()
5838
+ })
5839
+ ]);
5840
+ }
5841
+ });
5842
+ }
5843
+ if (rollbackOnError) {
5844
+ rules.push({
5845
+ id: `factory/${entityName}.rollback`,
5846
+ description: `Rolls back optimistic ${entityName} update on error`,
5847
+ eventTypes: [`${entityName}.error`],
5848
+ contract: {
5849
+ ruleId: `factory/${entityName}.rollback`,
5850
+ behavior: `Reverts to original ${entityName} state when mutation fails`,
5851
+ examples: [
5852
+ { given: "optimistic update was applied", when: "server returns error", then: "rollback emitted, optimistic retracted" }
5853
+ ],
5854
+ invariants: [
5855
+ "Rollback must restore original state exactly",
5856
+ "Optimistic facts must be retracted on rollback"
5857
+ ]
5858
+ },
5859
+ impl: (_state, events) => {
5860
+ const errorEvent = events.find((e) => e.tag === `${entityName}.error`);
5861
+ if (!errorEvent) return RuleResult.skip("No error event");
5862
+ const payload = errorEvent.payload;
5863
+ const result = RuleResult.emit([
5864
+ fact(`${entityName}.rollback`, {
5865
+ id: payload.id,
5866
+ error: payload.error,
5867
+ timestamp: Date.now()
5868
+ })
5869
+ ]);
5870
+ return result;
5871
+ }
5872
+ });
5873
+ }
5874
+ if (cacheInvalidation) {
5875
+ rules.push({
5876
+ id: `factory/${entityName}.cache-invalidate`,
5877
+ description: `Invalidates ${entityName} cache when data changes are confirmed`,
5878
+ eventTypes: [`${entityName}.confirmed`, `${entityName}.deleted`],
5879
+ contract: {
5880
+ ruleId: `factory/${entityName}.cache-invalidate`,
5881
+ behavior: `Emits cache invalidation signal when ${entityName} is confirmed or deleted`,
5882
+ examples: [
5883
+ { given: `${entityName} mutation confirmed`, when: "confirmed event fires", then: "cache.invalidate emitted" }
5884
+ ],
5885
+ invariants: ["Stale cache entries must be invalidated after confirmed mutations"]
5886
+ },
5887
+ impl: (_state, events) => {
5888
+ const confirmEvent = events.find(
5889
+ (e) => e.tag === `${entityName}.confirmed` || e.tag === `${entityName}.deleted`
5890
+ );
5891
+ if (!confirmEvent) return RuleResult.skip("No confirmation event");
5892
+ const payload = confirmEvent.payload;
5893
+ return RuleResult.emit([
5894
+ fact(`${entityName}.cache-invalidate`, {
5895
+ id: payload.id,
5896
+ timestamp: Date.now()
5897
+ })
5898
+ ]);
5899
+ }
5900
+ });
5901
+ }
5902
+ constraints.push({
5903
+ id: `factory/${entityName}.integrity`,
5904
+ description: `Ensures ${entityName} state integrity \u2014 no orphaned optimistic updates`,
5905
+ contract: {
5906
+ ruleId: `factory/${entityName}.integrity`,
5907
+ behavior: "Detects orphaned optimistic updates without pending confirmation",
5908
+ examples: [
5909
+ { given: "optimistic update exists without pending request", when: "checked", then: "violation" }
5910
+ ],
5911
+ invariants: [`Every optimistic ${entityName} update must have a corresponding pending request`]
5912
+ },
5913
+ impl: (state2) => {
5914
+ const pending = state2.context.pending ?? {};
5915
+ const optimisticFacts = state2.facts.filter((f) => f.tag === `${entityName}.optimistic`);
5916
+ for (const optFact of optimisticFacts) {
5917
+ const id = optFact.payload?.id;
5918
+ if (id && !pending[id]) {
5919
+ return `Orphaned optimistic update for ${entityName} id=${id} \u2014 no pending request`;
5920
+ }
5921
+ }
5922
+ return true;
5923
+ }
5924
+ });
5925
+ return { rules, constraints };
5926
+ }
5927
+
5928
+ // src/project/project.ts
5929
+ init_rule_result();
5930
+ function defineGate(name, config) {
5931
+ const { expects, onSatisfied, onViolation } = config;
5932
+ const rule = {
5933
+ id: `gate/${name}`,
5934
+ description: `Feature gate: ${name} \u2014 requires: ${expects.join(", ")}`,
5935
+ eventTypes: ["gate.check", `gate.${name}.check`],
5936
+ contract: {
5937
+ ruleId: `gate/${name}`,
5938
+ behavior: `Opens gate "${name}" when all expectations are met: ${expects.join(", ")}`,
5939
+ examples: [
5940
+ {
5941
+ given: `all expectations satisfied: ${expects.join(", ")}`,
5942
+ when: "gate checked",
5943
+ then: `gate.${name}.open emitted${onSatisfied ? ` \u2192 ${onSatisfied}` : ""}`
5944
+ },
5945
+ {
5946
+ given: "one or more expectations unsatisfied",
5947
+ when: "gate checked",
5948
+ then: `gate.${name}.blocked emitted${onViolation ? ` \u2192 ${onViolation}` : ""}`
5949
+ }
5950
+ ],
5951
+ invariants: [
5952
+ `Gate "${name}" must never open with unsatisfied expectations`,
5953
+ "Gate status must reflect current expectation state exactly"
5954
+ ]
5955
+ },
5956
+ impl: (state2, events) => {
5957
+ const gateEvent = events.find(
5958
+ (e) => e.tag === "gate.check" || e.tag === `gate.${name}.check`
5959
+ );
5960
+ if (!gateEvent) return RuleResult.skip("No gate check event");
5961
+ const expectationState = state2.context.expectations ?? {};
5962
+ const satisfied = [];
5963
+ const unsatisfied = [];
5964
+ for (const exp of expects) {
5965
+ if (expectationState[exp]) {
5966
+ satisfied.push(exp);
5967
+ } else {
5968
+ unsatisfied.push(exp);
5969
+ }
5970
+ }
5971
+ const status = unsatisfied.length === 0 ? "open" : "blocked";
5972
+ const gateState = {
5973
+ name,
5974
+ status,
5975
+ satisfied,
5976
+ unsatisfied,
5977
+ lastChanged: Date.now()
5978
+ };
5979
+ const facts = [fact(`gate.${name}.status`, gateState)];
5980
+ if (status === "open" && onSatisfied) {
5981
+ facts.push(fact(`gate.${name}.action`, { action: onSatisfied }));
5982
+ } else if (status === "blocked" && onViolation) {
5983
+ facts.push(fact(`gate.${name}.action`, { action: onViolation }));
5984
+ }
5985
+ return RuleResult.emit(facts);
5986
+ }
5987
+ };
5988
+ const constraint = {
5989
+ id: `gate/${name}/integrity`,
5990
+ description: `Ensures gate "${name}" status matches expectation reality`,
5991
+ contract: {
5992
+ ruleId: `gate/${name}/integrity`,
5993
+ behavior: `Validates that gate "${name}" is not open when expectations are unmet`,
5994
+ examples: [
5995
+ {
5996
+ given: `gate ${name} is open but expectations unmet`,
5997
+ when: "constraint checked",
5998
+ then: "violation"
5999
+ }
6000
+ ],
6001
+ invariants: [`Gate "${name}" must never report open when expectations are unsatisfied`]
6002
+ },
6003
+ impl: (state2) => {
6004
+ const gateState = state2.context.gates?.[name];
6005
+ if (!gateState) return true;
6006
+ if (gateState.status === "open" && gateState.unsatisfied.length > 0) {
6007
+ return `Gate "${name}" is open but has unsatisfied expectations: ${gateState.unsatisfied.join(", ")}`;
6008
+ }
6009
+ return true;
6010
+ }
6011
+ };
6012
+ return { rules: [rule], constraints: [constraint] };
6013
+ }
6014
+ function semverContract(config) {
6015
+ const { sources, invariants } = config;
6016
+ const rule = {
6017
+ id: "project/semver-check",
6018
+ description: `Checks version consistency across: ${sources.join(", ")}`,
6019
+ eventTypes: ["project.version-check"],
6020
+ contract: {
6021
+ ruleId: "project/semver-check",
6022
+ behavior: `Verifies version consistency across ${sources.length} sources`,
6023
+ examples: [
6024
+ {
6025
+ given: "all sources have version 1.2.3",
6026
+ when: "version check runs",
6027
+ then: "semver.consistent emitted"
6028
+ },
6029
+ {
6030
+ given: "package.json has 1.2.3 but README has 1.2.2",
6031
+ when: "version check runs",
6032
+ then: "semver.inconsistent emitted with diff"
6033
+ }
6034
+ ],
6035
+ invariants: invariants.length > 0 ? invariants : ["All version sources must report the same semver string"]
6036
+ },
6037
+ impl: (_state, events) => {
6038
+ const checkEvent = events.find((e) => e.tag === "project.version-check");
6039
+ if (!checkEvent) return RuleResult.skip("No version check event");
6040
+ const versions = checkEvent.payload?.versions ?? {};
6041
+ const versionValues = Object.values(versions);
6042
+ const unique = new Set(versionValues);
6043
+ if (unique.size <= 1) {
6044
+ return RuleResult.emit([
6045
+ fact("semver.consistent", {
6046
+ version: versionValues[0] ?? "unknown",
6047
+ sources: Object.keys(versions)
6048
+ })
6049
+ ]);
6050
+ }
6051
+ const report = {
6052
+ consistent: false,
6053
+ versions,
6054
+ violations: [`Version mismatch: ${JSON.stringify(versions)}`]
6055
+ };
6056
+ return RuleResult.emit([fact("semver.inconsistent", report)]);
6057
+ }
6058
+ };
6059
+ return { rules: [rule], constraints: [] };
6060
+ }
6061
+ function commitFromState(diff) {
6062
+ const parts = [];
6063
+ const bodyParts = [];
6064
+ const totalAdded = diff.rulesAdded.length + diff.contractsAdded.length + diff.expectationsAdded.length;
6065
+ const totalRemoved = diff.rulesRemoved.length + diff.contractsRemoved.length + diff.expectationsRemoved.length;
6066
+ const totalModified = diff.rulesModified.length;
6067
+ const hasGateChanges = diff.gateChanges.length > 0;
6068
+ if (totalAdded > 0 && totalRemoved === 0 && totalModified === 0) {
6069
+ if (diff.rulesAdded.length > 0) {
6070
+ const scope = inferScope(diff.rulesAdded);
6071
+ parts.push(`feat(${scope}): add ${formatIds(diff.rulesAdded)}`);
6072
+ } else if (diff.contractsAdded.length > 0) {
6073
+ parts.push(`feat(contracts): add contracts for ${formatIds(diff.contractsAdded)}`);
6074
+ } else {
6075
+ parts.push(`feat(expectations): add ${formatIds(diff.expectationsAdded)}`);
6076
+ }
6077
+ } else if (totalRemoved > 0 && totalAdded === 0) {
6078
+ if (diff.rulesRemoved.length > 0) {
6079
+ const scope = inferScope(diff.rulesRemoved);
6080
+ parts.push(`refactor(${scope}): remove ${formatIds(diff.rulesRemoved)}`);
6081
+ } else {
6082
+ parts.push(`refactor: remove ${totalRemoved} item(s)`);
6083
+ }
6084
+ } else if (totalModified > 0) {
6085
+ const scope = inferScope(diff.rulesModified);
6086
+ parts.push(`refactor(${scope}): update ${formatIds(diff.rulesModified)}`);
6087
+ } else if (hasGateChanges) {
6088
+ const gateNames = diff.gateChanges.map((g) => g.gate);
6089
+ parts.push(`chore(gates): ${formatIds(gateNames)} state changed`);
6090
+ } else {
6091
+ parts.push("chore: behavioral state update");
6092
+ }
6093
+ if (diff.rulesAdded.length > 0) bodyParts.push(`Rules added: ${diff.rulesAdded.join(", ")}`);
6094
+ if (diff.rulesRemoved.length > 0) bodyParts.push(`Rules removed: ${diff.rulesRemoved.join(", ")}`);
6095
+ if (diff.rulesModified.length > 0) bodyParts.push(`Rules modified: ${diff.rulesModified.join(", ")}`);
6096
+ if (diff.contractsAdded.length > 0) bodyParts.push(`Contracts added: ${diff.contractsAdded.join(", ")}`);
6097
+ if (diff.contractsRemoved.length > 0) bodyParts.push(`Contracts removed: ${diff.contractsRemoved.join(", ")}`);
6098
+ if (diff.expectationsAdded.length > 0) bodyParts.push(`Expectations added: ${diff.expectationsAdded.join(", ")}`);
6099
+ if (diff.expectationsRemoved.length > 0) bodyParts.push(`Expectations removed: ${diff.expectationsRemoved.join(", ")}`);
6100
+ for (const gc of diff.gateChanges) {
6101
+ bodyParts.push(`Gate "${gc.gate}": ${gc.from} \u2192 ${gc.to}`);
6102
+ }
6103
+ const subject = parts[0] || "chore: update";
6104
+ return bodyParts.length > 0 ? `${subject}
6105
+
6106
+ ${bodyParts.join("\n")}` : subject;
6107
+ }
6108
+ function inferScope(ids) {
6109
+ if (ids.length === 0) return "rules";
6110
+ const prefixes = ids.map((id) => {
6111
+ const slash = id.indexOf("/");
6112
+ return slash > 0 ? id.slice(0, slash) : id;
6113
+ });
6114
+ const unique = new Set(prefixes);
6115
+ return unique.size === 1 ? prefixes[0] : "rules";
6116
+ }
6117
+ function formatIds(ids) {
6118
+ if (ids.length <= 3) return ids.join(", ");
6119
+ return `${ids.slice(0, 2).join(", ")} (+${ids.length - 2} more)`;
6120
+ }
6121
+ function branchRules(config) {
6122
+ const { naming, mergeConditions } = config;
6123
+ const namePattern = naming.replace("{name}", "(.+)").replace("{issue}", "(\\d+)");
6124
+ const nameRegex = new RegExp(`^${namePattern}$`);
6125
+ const rule = {
6126
+ id: "project/branch-check",
6127
+ description: `Validates branch naming (${naming}) and merge conditions`,
6128
+ eventTypes: ["project.branch-check"],
6129
+ contract: {
6130
+ ruleId: "project/branch-check",
6131
+ behavior: `Ensures branch follows "${naming}" pattern and merge conditions are met`,
6132
+ examples: [
6133
+ {
6134
+ given: `branch named "${naming.replace("{name}", "my-feature")}"`,
6135
+ when: "branch checked",
6136
+ then: "branch.valid emitted"
6137
+ },
6138
+ {
6139
+ given: 'branch named "random-name"',
6140
+ when: "branch checked",
6141
+ then: "branch.invalid emitted"
6142
+ }
6143
+ ],
6144
+ invariants: [
6145
+ `Branch names must follow pattern: ${naming}`,
6146
+ `Merge requires: ${mergeConditions.join(", ")}`
6147
+ ]
6148
+ },
6149
+ impl: (_state, events) => {
6150
+ const checkEvent = events.find((e) => e.tag === "project.branch-check");
6151
+ if (!checkEvent) return RuleResult.skip("No branch check event");
6152
+ const payload = checkEvent.payload;
6153
+ const branch = payload.branch ?? "";
6154
+ const conditions = payload.conditions ?? {};
6155
+ const validName = nameRegex.test(branch);
6156
+ const unmetConditions = mergeConditions.filter((c) => !conditions[c]);
6157
+ if (validName && unmetConditions.length === 0) {
6158
+ return RuleResult.emit([
6159
+ fact("branch.valid", { branch, mergeReady: true })
6160
+ ]);
6161
+ }
6162
+ const reasons = [];
6163
+ if (!validName) reasons.push(`Branch name "${branch}" doesn't match pattern "${naming}"`);
6164
+ if (unmetConditions.length > 0) reasons.push(`Unmet merge conditions: ${unmetConditions.join(", ")}`);
6165
+ return RuleResult.emit([
6166
+ fact("branch.invalid", { branch, reasons, mergeReady: false })
6167
+ ]);
6168
+ }
6169
+ };
6170
+ return { rules: [rule], constraints: [] };
6171
+ }
6172
+ function lintGate(config = {}) {
6173
+ const expects = ["lint-passes", ...config.additionalExpects ?? []];
6174
+ return defineGate("lint", {
6175
+ expects,
6176
+ onSatisfied: "lint-passed",
6177
+ onViolation: "lint-failed"
6178
+ });
6179
+ }
6180
+ function formatGate(config = {}) {
6181
+ const expects = ["format-passes", ...config.additionalExpects ?? []];
6182
+ return defineGate("format", {
6183
+ expects,
6184
+ onSatisfied: "format-passed",
6185
+ onViolation: "format-failed"
6186
+ });
6187
+ }
6188
+ function expectationGate(config = {}) {
6189
+ const expects = ["expectations-verified", ...config.additionalExpects ?? []];
6190
+ return defineGate("expectations", {
6191
+ expects,
6192
+ onSatisfied: "expectations-passed",
6193
+ onViolation: "expectations-failed"
6194
+ });
6195
+ }
5102
6196
  // Annotate the CommonJS export names for ESM import in node:
5103
6197
  0 && (module.exports = {
5104
6198
  AcknowledgeContractGap,
@@ -5111,6 +6205,8 @@ function pct(a, b) {
5111
6205
  ContractMissing,
5112
6206
  ContractUpdated,
5113
6207
  ContractValidated,
6208
+ Expectation,
6209
+ ExpectationSet,
5114
6210
  FrameworkAgnosticReactiveEngine,
5115
6211
  InMemoryPraxisDB,
5116
6212
  LogicEngine,
@@ -5133,9 +6229,11 @@ function pct(a, b) {
5133
6229
  attachToEngine,
5134
6230
  attachUnumToEngine,
5135
6231
  auditCompleteness,
6232
+ branchRules,
5136
6233
  canvasToMermaid,
5137
6234
  canvasToSchema,
5138
6235
  canvasToYaml,
6236
+ commitFromState,
5139
6237
  createBehaviorLedger,
5140
6238
  createCanvasEditor,
5141
6239
  createChronicle,
@@ -5161,23 +6259,30 @@ function pct(a, b) {
5161
6259
  createUIModule,
5162
6260
  createUnifiedApp,
5163
6261
  createUnumAdapter,
6262
+ dataRules,
5164
6263
  defineConstraint,
5165
6264
  defineContract,
5166
6265
  defineEvent,
5167
6266
  defineFact,
6267
+ defineGate,
5168
6268
  defineModule,
5169
6269
  defineRule,
5170
6270
  dirtyGuardRule,
5171
6271
  errorDisplayRule,
6272
+ expectBehavior,
6273
+ expectationGate,
5172
6274
  fact,
5173
6275
  filterEvents,
5174
6276
  filterFacts,
5175
6277
  findEvent,
5176
6278
  findFact,
6279
+ formRules,
6280
+ formatGate,
5177
6281
  formatReport,
5178
6282
  formatValidationReport,
5179
6283
  formatValidationReportJSON,
5180
6284
  formatValidationReportSARIF,
6285
+ formatVerificationReport,
5181
6286
  generateDocs,
5182
6287
  generateId,
5183
6288
  generateTauriConfig,
@@ -5186,7 +6291,9 @@ function pct(a, b) {
5186
6291
  getFactPath,
5187
6292
  getSchemaPath,
5188
6293
  initGateRule,
6294
+ inputRules,
5189
6295
  isContract,
6296
+ lintGate,
5190
6297
  loadSchema,
5191
6298
  loadSchemaFromFile,
5192
6299
  loadSchemaFromJson,
@@ -5194,17 +6301,21 @@ function pct(a, b) {
5194
6301
  loadingGateRule,
5195
6302
  mustBeInitializedConstraint,
5196
6303
  navigationRequest,
6304
+ navigationRules,
5197
6305
  noInteractionWhileLoadingConstraint,
5198
6306
  offlineIndicatorRule,
5199
6307
  registerSchema,
5200
6308
  resizeEvent,
5201
6309
  runTerminalCommand,
5202
6310
  schemaToCanvas,
6311
+ semverContract,
6312
+ toastRules,
5203
6313
  uiModule,
5204
6314
  uiStateChanged,
5205
6315
  validateContracts,
5206
6316
  validateForGeneration,
5207
6317
  validateSchema,
5208
6318
  validateWithGuardian,
6319
+ verify,
5209
6320
  viewportRule
5210
6321
  });