@plures/praxis 1.4.0 → 1.4.4

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 (51) hide show
  1. package/dist/browser/{chunk-N63K4KWS.js → chunk-4IRUGWR3.js} +1 -1
  2. package/dist/browser/chunk-6SJ44Q64.js +473 -0
  3. package/dist/browser/chunk-BQOYZBWA.js +282 -0
  4. package/dist/browser/chunk-IG5BJ2MT.js +91 -0
  5. package/dist/browser/{chunk-MJK3IYTJ.js → chunk-JZDJU2DO.js} +4 -84
  6. package/dist/browser/chunk-ZEW4LJAJ.js +353 -0
  7. package/dist/browser/{engine-YIEGSX7U.js → engine-3B5WJPGT.js} +2 -1
  8. package/dist/browser/expectations/index.d.ts +180 -0
  9. package/dist/browser/expectations/index.js +14 -0
  10. package/dist/browser/factory/index.d.ts +149 -0
  11. package/dist/browser/factory/index.js +15 -0
  12. package/dist/browser/index.d.ts +274 -3
  13. package/dist/browser/index.js +407 -54
  14. package/dist/browser/integrations/svelte.d.ts +3 -2
  15. package/dist/browser/integrations/svelte.js +3 -2
  16. package/dist/browser/project/index.d.ts +176 -0
  17. package/dist/browser/project/index.js +19 -0
  18. package/dist/browser/reactive-engine.svelte-DgVTqHLc.d.ts +223 -0
  19. package/dist/browser/{reactive-engine.svelte-DjynI82A.d.ts → rules-i1LHpnGd.d.ts} +13 -221
  20. package/dist/node/chunk-AZLNISFI.js +1690 -0
  21. package/dist/node/chunk-IG5BJ2MT.js +91 -0
  22. package/dist/node/{chunk-KMJWAFZV.js → chunk-JZDJU2DO.js} +4 -89
  23. package/dist/node/{chunk-7M3HV4XR.js → chunk-ZO2LU4G4.js} +1 -1
  24. package/dist/node/cli/index.cjs +48 -0
  25. package/dist/node/cli/index.js +2 -2
  26. package/dist/node/{engine-FEN5IYZ5.js → engine-VFHCIEM4.js} +2 -1
  27. package/dist/node/index.cjs +1747 -0
  28. package/dist/node/index.d.cts +960 -278
  29. package/dist/node/index.d.ts +960 -278
  30. package/dist/node/index.js +559 -6
  31. package/dist/node/integrations/svelte.js +3 -2
  32. package/dist/node/{server-SYZPDULV.js → server-FKLVY57V.js} +4 -2
  33. package/dist/node/{validate-TQGVIG7G.js → validate-5PSWJTIC.js} +2 -1
  34. package/package.json +32 -11
  35. package/src/__tests__/chronos-project.test.ts +799 -0
  36. package/src/__tests__/decision-ledger.test.ts +857 -402
  37. package/src/chronos/diff.ts +336 -0
  38. package/src/chronos/hooks.ts +227 -0
  39. package/src/chronos/index.ts +83 -0
  40. package/src/chronos/project-chronicle.ts +198 -0
  41. package/src/chronos/timeline.ts +152 -0
  42. package/src/decision-ledger/analyzer-types.ts +280 -0
  43. package/src/decision-ledger/analyzer.ts +518 -0
  44. package/src/decision-ledger/contract-verification.ts +456 -0
  45. package/src/decision-ledger/derivation.ts +158 -0
  46. package/src/decision-ledger/index.ts +59 -0
  47. package/src/decision-ledger/report.ts +378 -0
  48. package/src/decision-ledger/suggestions.ts +287 -0
  49. package/src/index.browser.ts +83 -0
  50. package/src/index.ts +77 -0
  51. package/dist/node/chunk-FWOXU4MM.js +0 -487
@@ -622,12 +622,15 @@ __export(src_exports, {
622
622
  PraxisDBStore: () => PraxisDBStore,
623
623
  PraxisRegistry: () => PraxisRegistry,
624
624
  PraxisSchemaRegistry: () => PraxisSchemaRegistry,
625
+ ProjectChronicle: () => ProjectChronicle,
625
626
  ReactiveLogicEngine: () => ReactiveLogicEngine,
626
627
  RegistryIntrospector: () => RegistryIntrospector,
627
628
  RuleResult: () => RuleResult,
628
629
  StateDocsGenerator: () => StateDocsGenerator,
629
630
  TerminalAdapter: () => TerminalAdapter,
631
+ Timeline: () => Timeline,
630
632
  ValidateContracts: () => ValidateContracts,
633
+ analyzeDependencyGraph: () => analyzeDependencyGraph,
631
634
  attachAllIntegrations: () => attachAllIntegrations,
632
635
  attachTauriToEngine: () => attachTauriToEngine,
633
636
  attachToEngine: () => attachToEngine,
@@ -653,16 +656,19 @@ __export(src_exports, {
653
656
  createPraxisDBStore: () => createPraxisDBStore,
654
657
  createPraxisEngine: () => createPraxisEngine,
655
658
  createPraxisLocalFirst: () => createPraxisLocalFirst,
659
+ createProjectChronicle: () => createProjectChronicle,
656
660
  createReactiveEngine: () => createReactiveEngine,
657
661
  createSchemaRegistry: () => createSchemaRegistry,
658
662
  createSchemaTemplate: () => createSchemaTemplate,
659
663
  createStateDocsGenerator: () => createStateDocsGenerator,
660
664
  createTauriPraxisAdapter: () => createTauriPraxisAdapter,
661
665
  createTerminalAdapter: () => createTerminalAdapter,
666
+ createTimeline: () => createTimeline,
662
667
  createTimerActor: () => createTimerActor,
663
668
  createUIModule: () => createUIModule,
664
669
  createUnifiedApp: () => createUnifiedApp,
665
670
  createUnumAdapter: () => createUnumAdapter,
671
+ crossReferenceContracts: () => crossReferenceContracts,
666
672
  dataRules: () => dataRules,
667
673
  defineConstraint: () => defineConstraint,
668
674
  defineContract: () => defineContract,
@@ -671,17 +677,33 @@ __export(src_exports, {
671
677
  defineGate: () => defineGate,
672
678
  defineModule: () => defineModule,
673
679
  defineRule: () => defineRule,
680
+ diffContracts: () => diffContracts,
681
+ diffExpectations: () => diffExpectations,
682
+ diffLedgers: () => diffLedgers,
683
+ diffRegistries: () => diffRegistries,
674
684
  dirtyGuardRule: () => dirtyGuardRule,
685
+ enableProjectChronicle: () => enableProjectChronicle,
675
686
  errorDisplayRule: () => errorDisplayRule,
676
687
  expectBehavior: () => expectBehavior,
677
688
  expectationGate: () => expectationGate,
678
689
  fact: () => fact,
679
690
  filterEvents: () => filterEvents,
680
691
  filterFacts: () => filterFacts,
692
+ findContractGaps: () => findContractGaps,
693
+ findContradictions: () => findContradictions,
694
+ findDeadRules: () => findDeadRules,
681
695
  findEvent: () => findEvent,
682
696
  findFact: () => findFact,
697
+ findGaps: () => findGaps,
698
+ findShadowedRules: () => findShadowedRules,
699
+ findUnreachableStates: () => findUnreachableStates,
683
700
  formRules: () => formRules,
701
+ formatBehavioralCommit: () => formatCommitMessage,
702
+ formatBuildOutput: () => formatBuildOutput,
703
+ formatDelta: () => formatDelta,
684
704
  formatGate: () => formatGate,
705
+ formatLedger: () => formatLedger,
706
+ formatReleaseNotes: () => formatReleaseNotes,
685
707
  formatReport: () => formatReport,
686
708
  formatValidationReport: () => formatValidationReport,
687
709
  formatValidationReportJSON: () => formatValidationReportJSON,
@@ -689,6 +711,7 @@ __export(src_exports, {
689
711
  formatVerificationReport: () => formatVerificationReport,
690
712
  generateDocs: () => generateDocs,
691
713
  generateId: () => generateId,
714
+ generateLedger: () => generateLedger,
692
715
  generateTauriConfig: () => generateTauriConfig,
693
716
  getContract: () => getContract,
694
717
  getEventPath: () => getEventPath,
@@ -708,12 +731,17 @@ __export(src_exports, {
708
731
  navigationRules: () => navigationRules,
709
732
  noInteractionWhileLoadingConstraint: () => noInteractionWhileLoadingConstraint,
710
733
  offlineIndicatorRule: () => offlineIndicatorRule,
734
+ recordAudit: () => recordAudit,
711
735
  registerSchema: () => registerSchema,
712
736
  resizeEvent: () => resizeEvent,
713
737
  runTerminalCommand: () => runTerminalCommand,
714
738
  schemaToCanvas: () => schemaToCanvas,
715
739
  semverContract: () => semverContract,
740
+ suggest: () => suggest,
741
+ suggestAll: () => suggestAll,
716
742
  toastRules: () => toastRules,
743
+ traceDerivation: () => traceDerivation,
744
+ traceImpact: () => traceImpact,
717
745
  uiModule: () => uiModule,
718
746
  uiStateChanged: () => uiStateChanged,
719
747
  validateContracts: () => validateContracts,
@@ -721,6 +749,8 @@ __export(src_exports, {
721
749
  validateSchema: () => validateSchema,
722
750
  validateWithGuardian: () => validateWithGuardian,
723
751
  verify: () => verify,
752
+ verifyContractExamples: () => verifyContractExamples,
753
+ verifyInvariants: () => verifyInvariants,
724
754
  viewportRule: () => viewportRule
725
755
  });
726
756
  module.exports = __toCommonJS(src_exports);
@@ -2012,6 +2042,1190 @@ var import_node_path = __toESM(require("path"), 1);
2012
2042
  var import_node_fs2 = require("fs");
2013
2043
  var import_node_path2 = __toESM(require("path"), 1);
2014
2044
 
2045
+ // src/decision-ledger/analyzer.ts
2046
+ init_rule_result();
2047
+ function analyzeDependencyGraph(registry) {
2048
+ const facts = /* @__PURE__ */ new Map();
2049
+ const edges = [];
2050
+ const producers = /* @__PURE__ */ new Map();
2051
+ const consumers = /* @__PURE__ */ new Map();
2052
+ const rules = registry.getAllRules();
2053
+ for (const rule of rules) {
2054
+ const ruleId = rule.id;
2055
+ const produced = [];
2056
+ const consumed = [];
2057
+ const probeFacts = probeRuleExecution(rule);
2058
+ for (const tag of probeFacts.produced) {
2059
+ produced.push(tag);
2060
+ }
2061
+ for (const tag of probeFacts.consumed) {
2062
+ consumed.push(tag);
2063
+ }
2064
+ if (rule.contract) {
2065
+ for (const example of rule.contract.examples) {
2066
+ const thenTags = extractFactTagsFromText(example.then);
2067
+ for (const tag of thenTags) {
2068
+ if (!produced.includes(tag)) produced.push(tag);
2069
+ }
2070
+ const givenTags = extractFactTagsFromText(example.given);
2071
+ for (const tag of givenTags) {
2072
+ if (!consumed.includes(tag)) consumed.push(tag);
2073
+ }
2074
+ }
2075
+ }
2076
+ producers.set(ruleId, produced);
2077
+ consumers.set(ruleId, consumed);
2078
+ for (const tag of produced) {
2079
+ const node = getOrCreateFactNode(facts, tag);
2080
+ if (!node.producedBy.includes(ruleId)) {
2081
+ node.producedBy.push(ruleId);
2082
+ }
2083
+ edges.push({ from: ruleId, to: tag, type: "produces" });
2084
+ }
2085
+ for (const tag of consumed) {
2086
+ const node = getOrCreateFactNode(facts, tag);
2087
+ if (!node.consumedBy.includes(ruleId)) {
2088
+ node.consumedBy.push(ruleId);
2089
+ }
2090
+ edges.push({ from: tag, to: ruleId, type: "consumes" });
2091
+ }
2092
+ }
2093
+ return { facts, edges, producers, consumers };
2094
+ }
2095
+ function findDeadRules(registry, knownEventTypes) {
2096
+ const dead = [];
2097
+ const known = new Set(knownEventTypes);
2098
+ const rules = registry.getAllRules();
2099
+ for (const rule of rules) {
2100
+ if (!rule.eventTypes) continue;
2101
+ const required = Array.isArray(rule.eventTypes) ? rule.eventTypes : [rule.eventTypes];
2102
+ const hasMatch = required.some((t) => known.has(t));
2103
+ if (!hasMatch) {
2104
+ dead.push({
2105
+ ruleId: rule.id,
2106
+ description: rule.description,
2107
+ requiredEventTypes: required,
2108
+ reason: `Rule requires event types [${required.join(", ")}] but none are in the known event types [${knownEventTypes.join(", ")}]`
2109
+ });
2110
+ }
2111
+ }
2112
+ return dead;
2113
+ }
2114
+ function findUnreachableStates(registry) {
2115
+ const graph = analyzeDependencyGraph(registry);
2116
+ const unreachable = [];
2117
+ for (const [tag, node] of graph.facts) {
2118
+ if (node.producedBy.length === 0 && node.consumedBy.length > 0) {
2119
+ unreachable.push({
2120
+ factTags: [tag],
2121
+ reason: `Fact "${tag}" is consumed by rules [${node.consumedBy.join(", ")}] but never produced by any rule`
2122
+ });
2123
+ }
2124
+ }
2125
+ const allProducedTags = Array.from(graph.facts.keys()).filter(
2126
+ (tag) => graph.facts.get(tag).producedBy.length > 0
2127
+ );
2128
+ for (let i = 0; i < allProducedTags.length; i++) {
2129
+ for (let j = i + 1; j < allProducedTags.length; j++) {
2130
+ const tagA = allProducedTags[i];
2131
+ const tagB = allProducedTags[j];
2132
+ const producersA = graph.facts.get(tagA).producedBy;
2133
+ const producersB = graph.facts.get(tagB).producedBy;
2134
+ const sharedProducer = producersA.find((p) => producersB.includes(p));
2135
+ if (sharedProducer) continue;
2136
+ const rules = registry.getAllRules();
2137
+ const getEventTypes = (ruleId) => {
2138
+ const rule = rules.find((r) => r.id === ruleId);
2139
+ if (!rule?.eventTypes) return [];
2140
+ return Array.isArray(rule.eventTypes) ? rule.eventTypes : [rule.eventTypes];
2141
+ };
2142
+ const eventTypesA = new Set(producersA.flatMap(getEventTypes));
2143
+ const eventTypesB = new Set(producersB.flatMap(getEventTypes));
2144
+ if (eventTypesA.size > 0 && eventTypesB.size > 0 && ![...eventTypesA].some((t) => eventTypesB.has(t))) {
2145
+ const consumersA = graph.facts.get(tagA).consumedBy;
2146
+ const consumersB = graph.facts.get(tagB).consumedBy;
2147
+ const sharedConsumer = consumersA.find((c) => consumersB.includes(c));
2148
+ if (sharedConsumer) {
2149
+ unreachable.push({
2150
+ factTags: [tagA, tagB],
2151
+ reason: `Facts "${tagA}" and "${tagB}" are both consumed by rule "${sharedConsumer}" but are produced by rules with non-overlapping event types`
2152
+ });
2153
+ }
2154
+ }
2155
+ }
2156
+ }
2157
+ return unreachable;
2158
+ }
2159
+ function findShadowedRules(registry) {
2160
+ const shadowed = [];
2161
+ const graph = analyzeDependencyGraph(registry);
2162
+ const rules = registry.getAllRules();
2163
+ for (let i = 0; i < rules.length; i++) {
2164
+ for (let j = 0; j < rules.length; j++) {
2165
+ if (i === j) continue;
2166
+ const ruleA = rules[i];
2167
+ const ruleB = rules[j];
2168
+ const typesA = normalizeEventTypes(ruleA.eventTypes);
2169
+ const typesB = normalizeEventTypes(ruleB.eventTypes);
2170
+ if (typesA.length === 0 || typesB.length === 0) continue;
2171
+ const shared = typesA.filter((t) => typesB.includes(t));
2172
+ if (shared.length === 0) continue;
2173
+ const producedA = graph.producers.get(ruleA.id) ?? [];
2174
+ const producedB = graph.producers.get(ruleB.id) ?? [];
2175
+ if (producedA.length === 0) continue;
2176
+ const isSuperset = producedA.every((tag) => producedB.includes(tag));
2177
+ const isProperSuperset = isSuperset && producedB.length > producedA.length;
2178
+ if (isProperSuperset) {
2179
+ shadowed.push({
2180
+ ruleId: ruleA.id,
2181
+ shadowedBy: ruleB.id,
2182
+ sharedEventTypes: shared,
2183
+ reason: `Rule "${ruleB.id}" produces a superset of facts [${producedB.join(", ")}] compared to "${ruleA.id}" [${producedA.join(", ")}] for the same event types [${shared.join(", ")}]`
2184
+ });
2185
+ }
2186
+ }
2187
+ }
2188
+ return shadowed;
2189
+ }
2190
+ function findContradictions(registry) {
2191
+ const contradictions = [];
2192
+ const graph = analyzeDependencyGraph(registry);
2193
+ const rules = registry.getAllRules();
2194
+ for (const [tag, node] of graph.facts) {
2195
+ if (node.producedBy.length < 2) continue;
2196
+ for (let i = 0; i < node.producedBy.length; i++) {
2197
+ for (let j = i + 1; j < node.producedBy.length; j++) {
2198
+ const ruleIdA = node.producedBy[i];
2199
+ const ruleIdB = node.producedBy[j];
2200
+ const ruleA = rules.find((r) => r.id === ruleIdA);
2201
+ const ruleB = rules.find((r) => r.id === ruleIdB);
2202
+ if (!ruleA || !ruleB) continue;
2203
+ const typesA = normalizeEventTypes(ruleA.eventTypes);
2204
+ const typesB = normalizeEventTypes(ruleB.eventTypes);
2205
+ const bothCatchAll = typesA.length === 0 && typesB.length === 0;
2206
+ const sharedTypes = typesA.filter((t) => typesB.includes(t));
2207
+ const hasOverlap = sharedTypes.length > 0;
2208
+ if (bothCatchAll || hasOverlap) {
2209
+ const conflictDetail = checkContractConflict(ruleA, ruleB, tag);
2210
+ if (conflictDetail || bothCatchAll || hasOverlap) {
2211
+ contradictions.push({
2212
+ ruleA: ruleIdA,
2213
+ ruleB: ruleIdB,
2214
+ conflictingTag: tag,
2215
+ reason: conflictDetail ?? `Rules "${ruleIdA}" and "${ruleIdB}" both produce fact "${tag}" and respond to ${bothCatchAll ? "all events" : `event types [${sharedTypes.join(", ")}]`}`
2216
+ });
2217
+ }
2218
+ }
2219
+ }
2220
+ }
2221
+ }
2222
+ return contradictions;
2223
+ }
2224
+ function findGaps(registry, expectations) {
2225
+ const gaps = [];
2226
+ const rules = registry.getAllRules();
2227
+ const constraints = registry.getAllConstraints();
2228
+ for (const exp of expectations.expectations) {
2229
+ const nameLower = exp.name.toLowerCase();
2230
+ const nameParts = nameLower.split(/[-_./\s]+/);
2231
+ const related = rules.filter((r) => {
2232
+ const idLower = r.id.toLowerCase();
2233
+ const descLower = r.description.toLowerCase();
2234
+ const behaviorLower = r.contract?.behavior?.toLowerCase() ?? "";
2235
+ if (idLower.includes(nameLower) || nameLower.includes(idLower)) return true;
2236
+ if (descLower.includes(nameLower) || behaviorLower.includes(nameLower)) return true;
2237
+ const minParts = Math.min(2, nameParts.length);
2238
+ const matches = nameParts.filter(
2239
+ (part) => part.length > 2 && (idLower.includes(part) || descLower.includes(part))
2240
+ );
2241
+ return matches.length >= minParts;
2242
+ });
2243
+ const relatedConstraints = constraints.filter((c) => {
2244
+ const idLower = c.id.toLowerCase();
2245
+ const descLower = c.description.toLowerCase();
2246
+ return idLower.includes(nameLower) || nameLower.includes(idLower) || descLower.includes(nameLower);
2247
+ });
2248
+ if (related.length === 0 && relatedConstraints.length === 0) {
2249
+ gaps.push({
2250
+ expectationName: exp.name,
2251
+ description: `No rules or constraints found for expectation "${exp.name}"`,
2252
+ partialCoverage: [],
2253
+ type: "no-rule"
2254
+ });
2255
+ continue;
2256
+ }
2257
+ const uncoveredConditions = exp.conditions.filter((cond) => {
2258
+ const condLower = cond.description.toLowerCase();
2259
+ return !related.some(
2260
+ (r) => r.contract?.examples.some(
2261
+ (ex) => ex.given.toLowerCase().includes(condLower) || ex.when.toLowerCase().includes(condLower) || ex.then.toLowerCase().includes(condLower) || condLower.includes(ex.given.toLowerCase()) || condLower.includes(ex.when.toLowerCase())
2262
+ ) || r.contract?.invariants.some(
2263
+ (inv) => inv.toLowerCase().includes(condLower) || condLower.includes(inv.toLowerCase())
2264
+ ) || r.contract?.behavior.toLowerCase().includes(condLower) || r.description.toLowerCase().includes(condLower)
2265
+ );
2266
+ });
2267
+ if (uncoveredConditions.length > 0 && uncoveredConditions.length < exp.conditions.length) {
2268
+ gaps.push({
2269
+ expectationName: exp.name,
2270
+ description: `Expectation "${exp.name}" is partially covered. Uncovered conditions: ${uncoveredConditions.map((c) => c.description).join("; ")}`,
2271
+ partialCoverage: related.map((r) => r.id),
2272
+ type: "partial-coverage"
2273
+ });
2274
+ } else if (uncoveredConditions.length === exp.conditions.length && exp.conditions.length > 0) {
2275
+ const rulesWithoutContracts = related.filter((r) => !r.contract);
2276
+ if (rulesWithoutContracts.length > 0) {
2277
+ gaps.push({
2278
+ expectationName: exp.name,
2279
+ description: `Rules related to "${exp.name}" lack contracts: [${rulesWithoutContracts.map((r) => r.id).join(", ")}]`,
2280
+ partialCoverage: related.map((r) => r.id),
2281
+ type: "no-contract"
2282
+ });
2283
+ }
2284
+ }
2285
+ }
2286
+ return gaps;
2287
+ }
2288
+ function getOrCreateFactNode(facts, tag) {
2289
+ let node = facts.get(tag);
2290
+ if (!node) {
2291
+ node = { tag, producedBy: [], consumedBy: [] };
2292
+ facts.set(tag, node);
2293
+ }
2294
+ return node;
2295
+ }
2296
+ function normalizeEventTypes(eventTypes) {
2297
+ if (!eventTypes) return [];
2298
+ return Array.isArray(eventTypes) ? eventTypes : [eventTypes];
2299
+ }
2300
+ function probeRuleExecution(rule) {
2301
+ const produced = [];
2302
+ const consumed = [];
2303
+ const eventTypes = normalizeEventTypes(rule.eventTypes);
2304
+ const syntheticEvents = eventTypes.length > 0 ? eventTypes.map((tag) => ({ tag, payload: {} })) : [{ tag: "__probe__", payload: {} }];
2305
+ const syntheticState = {
2306
+ context: {},
2307
+ facts: [],
2308
+ events: syntheticEvents,
2309
+ meta: {},
2310
+ protocolVersion: "1.0.0"
2311
+ };
2312
+ try {
2313
+ const result = rule.impl(syntheticState, syntheticEvents);
2314
+ if (result instanceof RuleResult) {
2315
+ if (result.kind === "emit") {
2316
+ for (const fact2 of result.facts) {
2317
+ if (!produced.includes(fact2.tag)) {
2318
+ produced.push(fact2.tag);
2319
+ }
2320
+ }
2321
+ } else if (result.kind === "retract") {
2322
+ for (const tag of result.retractTags) {
2323
+ if (!consumed.includes(tag)) {
2324
+ consumed.push(tag);
2325
+ }
2326
+ }
2327
+ }
2328
+ } else if (Array.isArray(result)) {
2329
+ for (const fact2 of result) {
2330
+ if (fact2.tag && !produced.includes(fact2.tag)) {
2331
+ produced.push(fact2.tag);
2332
+ }
2333
+ }
2334
+ }
2335
+ } catch {
2336
+ }
2337
+ return { produced, consumed };
2338
+ }
2339
+ function extractFactTagsFromText(text) {
2340
+ const tags = [];
2341
+ const patterns = [
2342
+ /(?:emit|produce|retract|read|consume|fact)\s+['"]?([a-zA-Z][a-zA-Z0-9._-]+)['"]?/gi,
2343
+ /['"]([a-zA-Z][a-zA-Z0-9._-]*\.[a-zA-Z][a-zA-Z0-9._-]*)['"]?/g
2344
+ ];
2345
+ for (const pattern of patterns) {
2346
+ let match;
2347
+ while ((match = pattern.exec(text)) !== null) {
2348
+ const tag = match[1];
2349
+ if (tag && !tags.includes(tag)) {
2350
+ tags.push(tag);
2351
+ }
2352
+ }
2353
+ }
2354
+ return tags;
2355
+ }
2356
+ function checkContractConflict(ruleA, ruleB, _factTag) {
2357
+ if (!ruleA.contract || !ruleB.contract) return null;
2358
+ for (const exA of ruleA.contract.examples) {
2359
+ for (const exB of ruleB.contract.examples) {
2360
+ const sameGiven = exA.given.toLowerCase() === exB.given.toLowerCase();
2361
+ const sameWhen = exA.when.toLowerCase() === exB.when.toLowerCase();
2362
+ const differentThen = exA.then.toLowerCase() !== exB.then.toLowerCase();
2363
+ if ((sameGiven || sameWhen) && differentThen) {
2364
+ return `Contract conflict: "${ruleA.id}" expects "${exA.then}" but "${ruleB.id}" expects "${exB.then}" under similar conditions`;
2365
+ }
2366
+ }
2367
+ }
2368
+ return null;
2369
+ }
2370
+
2371
+ // src/decision-ledger/derivation.ts
2372
+ function traceDerivation(factTag, _engine, registry) {
2373
+ const graph = analyzeDependencyGraph(registry);
2374
+ const steps = [];
2375
+ const visited = /* @__PURE__ */ new Set();
2376
+ function walkBackward(tag, depth) {
2377
+ if (visited.has(tag) || depth > 20) return;
2378
+ visited.add(tag);
2379
+ const factNode = graph.facts.get(tag);
2380
+ if (!factNode) {
2381
+ steps.unshift({
2382
+ type: "fact-produced",
2383
+ id: tag,
2384
+ description: `Fact "${tag}" (origin unknown \u2014 not in dependency graph)`
2385
+ });
2386
+ return;
2387
+ }
2388
+ steps.unshift({
2389
+ type: "fact-produced",
2390
+ id: tag,
2391
+ description: `Fact "${tag}" produced`
2392
+ });
2393
+ for (const ruleId of factNode.producedBy) {
2394
+ if (visited.has(ruleId)) continue;
2395
+ visited.add(ruleId);
2396
+ const rule = registry.getRule(ruleId);
2397
+ steps.unshift({
2398
+ type: "rule-fired",
2399
+ id: ruleId,
2400
+ description: `Rule "${ruleId}" fired${rule ? `: ${rule.description}` : ""}`
2401
+ });
2402
+ const consumed = graph.consumers.get(ruleId) ?? [];
2403
+ for (const consumedTag of consumed) {
2404
+ steps.unshift({
2405
+ type: "fact-read",
2406
+ id: consumedTag,
2407
+ description: `Rule "${ruleId}" reads fact "${consumedTag}"`
2408
+ });
2409
+ walkBackward(consumedTag, depth + 1);
2410
+ }
2411
+ if (rule?.eventTypes) {
2412
+ const types = Array.isArray(rule.eventTypes) ? rule.eventTypes : [rule.eventTypes];
2413
+ for (const eventType of types) {
2414
+ steps.unshift({
2415
+ type: "event",
2416
+ id: eventType,
2417
+ description: `Event "${eventType}" triggers rule "${ruleId}"`
2418
+ });
2419
+ }
2420
+ }
2421
+ }
2422
+ }
2423
+ walkBackward(factTag, 0);
2424
+ const seen = /* @__PURE__ */ new Set();
2425
+ const dedupedSteps = steps.filter((step) => {
2426
+ const key = `${step.type}:${step.id}`;
2427
+ if (seen.has(key)) return false;
2428
+ seen.add(key);
2429
+ return true;
2430
+ });
2431
+ return {
2432
+ targetFact: factTag,
2433
+ steps: dedupedSteps,
2434
+ depth: dedupedSteps.filter((s) => s.type === "rule-fired").length
2435
+ };
2436
+ }
2437
+ function traceImpact(factTag, registry) {
2438
+ const graph = analyzeDependencyGraph(registry);
2439
+ const affectedRules = [];
2440
+ const affectedFacts = [];
2441
+ const visited = /* @__PURE__ */ new Set();
2442
+ function walkForward(tag, depth2) {
2443
+ if (visited.has(tag) || depth2 > 20) return depth2;
2444
+ visited.add(tag);
2445
+ const factNode = graph.facts.get(tag);
2446
+ if (!factNode) return depth2;
2447
+ let maxDepth = depth2;
2448
+ for (const ruleId of factNode.consumedBy) {
2449
+ if (!affectedRules.includes(ruleId)) {
2450
+ affectedRules.push(ruleId);
2451
+ }
2452
+ const produced = graph.producers.get(ruleId) ?? [];
2453
+ for (const producedTag of produced) {
2454
+ if (producedTag !== factTag && !affectedFacts.includes(producedTag)) {
2455
+ affectedFacts.push(producedTag);
2456
+ const childDepth = walkForward(producedTag, depth2 + 1);
2457
+ if (childDepth > maxDepth) maxDepth = childDepth;
2458
+ }
2459
+ }
2460
+ }
2461
+ return maxDepth;
2462
+ }
2463
+ const depth = walkForward(factTag, 0);
2464
+ return {
2465
+ factTag,
2466
+ affectedRules,
2467
+ affectedFacts,
2468
+ depth
2469
+ };
2470
+ }
2471
+
2472
+ // src/decision-ledger/contract-verification.ts
2473
+ init_rule_result();
2474
+ function verifyContractExamples(rule, contract) {
2475
+ const examples = [];
2476
+ for (let i = 0; i < contract.examples.length; i++) {
2477
+ const example = contract.examples[i];
2478
+ try {
2479
+ const state2 = buildStateFromDescription(example.given);
2480
+ const events = buildEventsFromDescription(example.when, rule.eventTypes);
2481
+ const stateWithEvents = { ...state2, events };
2482
+ const result = rule.impl(stateWithEvents, events);
2483
+ const verification = verifyOutput(result, example.then, rule.id);
2484
+ examples.push({
2485
+ index: i,
2486
+ given: example.given,
2487
+ when: example.when,
2488
+ expectedThen: example.then,
2489
+ passed: verification.passed,
2490
+ actualOutput: verification.actualOutput,
2491
+ error: verification.error
2492
+ });
2493
+ } catch (error) {
2494
+ examples.push({
2495
+ index: i,
2496
+ given: example.given,
2497
+ when: example.when,
2498
+ expectedThen: example.then,
2499
+ passed: false,
2500
+ error: `Execution error: ${error instanceof Error ? error.message : String(error)}`
2501
+ });
2502
+ }
2503
+ }
2504
+ const passCount = examples.filter((e) => e.passed).length;
2505
+ const failCount = examples.filter((e) => !e.passed).length;
2506
+ return {
2507
+ ruleId: rule.id,
2508
+ examples,
2509
+ allPassed: failCount === 0,
2510
+ passCount,
2511
+ failCount
2512
+ };
2513
+ }
2514
+ function verifyInvariants(registry) {
2515
+ const checks = [];
2516
+ const rules = registry.getAllRules();
2517
+ for (const rule of rules) {
2518
+ if (!rule.contract) continue;
2519
+ for (const invariant of rule.contract.invariants) {
2520
+ const consistent = rule.contract.examples.every((example) => {
2521
+ return isConsistentWithInvariant(example, invariant);
2522
+ });
2523
+ checks.push({
2524
+ invariant,
2525
+ ruleId: rule.id,
2526
+ holds: consistent,
2527
+ explanation: consistent ? `Invariant "${invariant}" is consistent with all ${rule.contract.examples.length} examples` : `Invariant "${invariant}" may be violated by one or more examples`
2528
+ });
2529
+ }
2530
+ }
2531
+ return checks;
2532
+ }
2533
+ function findContractGaps(registry) {
2534
+ const gaps = [];
2535
+ const rules = registry.getAllRules();
2536
+ for (const rule of rules) {
2537
+ if (!rule.contract) continue;
2538
+ const examples = rule.contract.examples;
2539
+ const hasErrorExamples = examples.some(
2540
+ (ex) => ex.then.toLowerCase().includes("error") || ex.then.toLowerCase().includes("fail") || ex.then.toLowerCase().includes("skip") || ex.then.toLowerCase().includes("noop") || ex.then.toLowerCase().includes("retract") || ex.then.toLowerCase().includes("violation")
2541
+ );
2542
+ if (!hasErrorExamples && examples.length > 0) {
2543
+ gaps.push({
2544
+ ruleId: rule.id,
2545
+ description: `No error/failure path examples. All ${examples.length} examples show happy paths`,
2546
+ type: "missing-error-path"
2547
+ });
2548
+ }
2549
+ if (rule.eventTypes) {
2550
+ const eventTypes = Array.isArray(rule.eventTypes) ? rule.eventTypes : [rule.eventTypes];
2551
+ if (eventTypes.length > 1) {
2552
+ const coveredTypes = /* @__PURE__ */ new Set();
2553
+ for (const ex of examples) {
2554
+ for (const et of eventTypes) {
2555
+ if (ex.when.toLowerCase().includes(et.toLowerCase()) || ex.when.toLowerCase().includes(et.replace(".", " ").toLowerCase())) {
2556
+ coveredTypes.add(et);
2557
+ }
2558
+ }
2559
+ }
2560
+ const uncovered = eventTypes.filter((et) => !coveredTypes.has(et));
2561
+ if (uncovered.length > 0) {
2562
+ gaps.push({
2563
+ ruleId: rule.id,
2564
+ description: `Event types [${uncovered.join(", ")}] not covered by any example`,
2565
+ type: "missing-edge-case"
2566
+ });
2567
+ }
2568
+ }
2569
+ }
2570
+ if (examples.length === 1) {
2571
+ gaps.push({
2572
+ ruleId: rule.id,
2573
+ description: `Only 1 contract example \u2014 likely missing boundary conditions and edge cases`,
2574
+ type: "missing-boundary"
2575
+ });
2576
+ }
2577
+ }
2578
+ return gaps;
2579
+ }
2580
+ function crossReferenceContracts(registry) {
2581
+ const graph = analyzeDependencyGraph(registry);
2582
+ const references = [];
2583
+ const rules = registry.getAllRules();
2584
+ for (const rule of rules) {
2585
+ if (!rule.contract) continue;
2586
+ for (const example of rule.contract.examples) {
2587
+ const referencedTags = extractReferencedFactTags(example.given + " " + example.when);
2588
+ for (const tag of referencedTags) {
2589
+ const factNode = graph.facts.get(tag);
2590
+ const producerRuleId = factNode?.producedBy[0] ?? null;
2591
+ const valid = factNode ? factNode.producedBy.length > 0 : false;
2592
+ if (producerRuleId === rule.id) continue;
2593
+ references.push({
2594
+ sourceRuleId: rule.id,
2595
+ referencedFactTag: tag,
2596
+ producerRuleId,
2597
+ valid
2598
+ });
2599
+ }
2600
+ }
2601
+ }
2602
+ return references;
2603
+ }
2604
+ function buildStateFromDescription(given) {
2605
+ const context = {};
2606
+ const facts = [];
2607
+ const factPattern = /fact\s+['"]([^'"]+)['"]/gi;
2608
+ let match;
2609
+ while ((match = factPattern.exec(given)) !== null) {
2610
+ facts.push({ tag: match[1], payload: {} });
2611
+ }
2612
+ const dottedPattern = /['"]([a-zA-Z][a-zA-Z0-9]*\.[a-zA-Z][a-zA-Z0-9.]*)['"]/g;
2613
+ while ((match = dottedPattern.exec(given)) !== null) {
2614
+ const tag = match[1];
2615
+ if (!facts.some((f) => f.tag === tag)) {
2616
+ facts.push({ tag, payload: {} });
2617
+ }
2618
+ }
2619
+ return {
2620
+ context,
2621
+ facts,
2622
+ meta: {},
2623
+ protocolVersion: "1.0.0"
2624
+ };
2625
+ }
2626
+ function buildEventsFromDescription(when, eventTypes) {
2627
+ const events = [];
2628
+ if (eventTypes) {
2629
+ const types = Array.isArray(eventTypes) ? eventTypes : [eventTypes];
2630
+ for (const type of types) {
2631
+ if (when.toLowerCase().includes(type.toLowerCase()) || when.toLowerCase().includes(type.replace(".", " ").toLowerCase())) {
2632
+ events.push({ tag: type, payload: {} });
2633
+ }
2634
+ }
2635
+ }
2636
+ if (events.length === 0) {
2637
+ const eventPattern = /['"]([A-Z][A-Z0-9._-]+)['"]/g;
2638
+ let match;
2639
+ while ((match = eventPattern.exec(when)) !== null) {
2640
+ events.push({ tag: match[1], payload: {} });
2641
+ }
2642
+ }
2643
+ if (events.length === 0) {
2644
+ const types = eventTypes ? Array.isArray(eventTypes) ? eventTypes : [eventTypes] : ["__test__"];
2645
+ events.push({ tag: types[0], payload: {} });
2646
+ }
2647
+ return events;
2648
+ }
2649
+ function verifyOutput(result, expectedThen, ruleId) {
2650
+ const thenLower = expectedThen.toLowerCase();
2651
+ if (result instanceof RuleResult) {
2652
+ switch (result.kind) {
2653
+ case "emit": {
2654
+ const factTags = result.facts.map((f) => f.tag);
2655
+ const actualOutput = `Emitted: [${factTags.join(", ")}]`;
2656
+ const emitExpected = thenLower.includes("emit") || thenLower.includes("produce") || thenLower.includes("fact");
2657
+ if (emitExpected) {
2658
+ const expectedTags = extractReferencedFactTags(expectedThen);
2659
+ if (expectedTags.length > 0) {
2660
+ const allFound = expectedTags.every(
2661
+ (tag) => factTags.some((ft) => ft.toLowerCase() === tag.toLowerCase())
2662
+ );
2663
+ return { passed: allFound, actualOutput };
2664
+ }
2665
+ return { passed: true, actualOutput };
2666
+ }
2667
+ return { passed: true, actualOutput };
2668
+ }
2669
+ case "noop": {
2670
+ const expectNoop = thenLower.includes("noop") || thenLower.includes("nothing") || thenLower.includes("no fact") || thenLower.includes("skip");
2671
+ return {
2672
+ passed: expectNoop,
2673
+ actualOutput: `Noop: ${result.reason ?? "no reason"}`
2674
+ };
2675
+ }
2676
+ case "skip": {
2677
+ const expectSkip = thenLower.includes("skip") || thenLower.includes("noop") || thenLower.includes("not fire") || thenLower.includes("nothing");
2678
+ return {
2679
+ passed: expectSkip,
2680
+ actualOutput: `Skip: ${result.reason ?? "no reason"}`
2681
+ };
2682
+ }
2683
+ case "retract": {
2684
+ const expectRetract = thenLower.includes("retract") || thenLower.includes("remove") || thenLower.includes("clear");
2685
+ return {
2686
+ passed: expectRetract,
2687
+ actualOutput: `Retract: [${result.retractTags.join(", ")}]`
2688
+ };
2689
+ }
2690
+ }
2691
+ } else if (Array.isArray(result)) {
2692
+ const factTags = result.map((f) => f.tag);
2693
+ const actualOutput = `Facts: [${factTags.join(", ")}]`;
2694
+ return { passed: factTags.length > 0 || thenLower.includes("nothing"), actualOutput };
2695
+ }
2696
+ return { passed: false, error: `Unexpected result type from rule "${ruleId}"` };
2697
+ }
2698
+ function isConsistentWithInvariant(example, invariant) {
2699
+ const invLower = invariant.toLowerCase();
2700
+ const thenLower = example.then.toLowerCase();
2701
+ if (invLower.includes("must not") || invLower.includes("never")) {
2702
+ const keyword = invLower.replace(/must not|never|should not/g, "").trim().split(/\s+/)[0];
2703
+ if (keyword && thenLower.includes(keyword)) {
2704
+ return false;
2705
+ }
2706
+ }
2707
+ if (invLower.includes("must ") && !invLower.includes("must not")) {
2708
+ const mustPart = invLower.split("must ")[1]?.split(/[.,;]/)[0]?.trim();
2709
+ if (mustPart) {
2710
+ const negations = ["no ", "not ", "never ", "without "];
2711
+ for (const neg of negations) {
2712
+ if (thenLower.includes(neg + mustPart.split(/\s+/)[0])) {
2713
+ return false;
2714
+ }
2715
+ }
2716
+ }
2717
+ }
2718
+ return true;
2719
+ }
2720
+ function extractReferencedFactTags(text) {
2721
+ const tags = [];
2722
+ const patterns = [
2723
+ /['"]([a-zA-Z][a-zA-Z0-9]*\.[a-zA-Z][a-zA-Z0-9.]*)['"]/g,
2724
+ /fact\s+['"]?([a-zA-Z][a-zA-Z0-9._-]+)['"]?/gi,
2725
+ /(?:emit|produce|retract)\s+['"]?([a-zA-Z][a-zA-Z0-9._-]+)['"]?/gi
2726
+ ];
2727
+ for (const pattern of patterns) {
2728
+ let match;
2729
+ while ((match = pattern.exec(text)) !== null) {
2730
+ const tag = match[1];
2731
+ if (tag && !tags.includes(tag)) {
2732
+ tags.push(tag);
2733
+ }
2734
+ }
2735
+ }
2736
+ return tags;
2737
+ }
2738
+
2739
+ // src/decision-ledger/suggestions.ts
2740
+ function suggest(finding, type) {
2741
+ switch (type) {
2742
+ case "dead-rule":
2743
+ return suggestForDeadRule(finding);
2744
+ case "gap":
2745
+ return suggestForGap(finding);
2746
+ case "contradiction":
2747
+ return suggestForContradiction(finding);
2748
+ case "unreachable-state":
2749
+ return suggestForUnreachableState(finding);
2750
+ case "shadowed-rule":
2751
+ return suggestForShadowedRule(finding);
2752
+ case "contract-gap":
2753
+ return suggestForContractGap(finding);
2754
+ default:
2755
+ return {
2756
+ findingType: type,
2757
+ entityId: "unknown",
2758
+ message: "Unknown finding type",
2759
+ action: "modify",
2760
+ priority: 1
2761
+ };
2762
+ }
2763
+ }
2764
+ function suggestAll(findings) {
2765
+ const suggestions = [];
2766
+ if (findings.deadRules) {
2767
+ for (const f of findings.deadRules) {
2768
+ suggestions.push(suggestForDeadRule(f));
2769
+ }
2770
+ }
2771
+ if (findings.gaps) {
2772
+ for (const f of findings.gaps) {
2773
+ suggestions.push(suggestForGap(f));
2774
+ }
2775
+ }
2776
+ if (findings.contradictions) {
2777
+ for (const f of findings.contradictions) {
2778
+ suggestions.push(suggestForContradiction(f));
2779
+ }
2780
+ }
2781
+ if (findings.unreachableStates) {
2782
+ for (const f of findings.unreachableStates) {
2783
+ suggestions.push(suggestForUnreachableState(f));
2784
+ }
2785
+ }
2786
+ if (findings.shadowedRules) {
2787
+ for (const f of findings.shadowedRules) {
2788
+ suggestions.push(suggestForShadowedRule(f));
2789
+ }
2790
+ }
2791
+ if (findings.contractGaps) {
2792
+ for (const f of findings.contractGaps) {
2793
+ suggestions.push(suggestForContractGap(f));
2794
+ }
2795
+ }
2796
+ suggestions.sort((a, b) => b.priority - a.priority);
2797
+ return suggestions;
2798
+ }
2799
+ function suggestForDeadRule(finding) {
2800
+ const eventList = finding.requiredEventTypes.join(", ");
2801
+ return {
2802
+ findingType: "dead-rule",
2803
+ entityId: finding.ruleId,
2804
+ message: `Remove rule "${finding.ruleId}" or add event type "${finding.requiredEventTypes[0]}" to your event sources. Rule requires [${eventList}] but none are emitted by the application.`,
2805
+ action: finding.requiredEventTypes.length === 1 ? "add-event-type" : "remove",
2806
+ priority: 5,
2807
+ skeleton: `// Option 1: Remove the dead rule
2808
+ registry.removeRule('${finding.ruleId}');
2809
+
2810
+ // Option 2: Add the missing event type
2811
+ engine.step([{ tag: '${finding.requiredEventTypes[0]}', payload: {} }]);`
2812
+ };
2813
+ }
2814
+ function suggestForGap(finding) {
2815
+ const ruleId = finding.expectationName.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
2816
+ switch (finding.type) {
2817
+ case "no-rule":
2818
+ return {
2819
+ findingType: "gap",
2820
+ entityId: finding.expectationName,
2821
+ message: `Add a rule covering: "${finding.expectationName}". No rules or constraints address this expected behavior.`,
2822
+ action: "add-rule",
2823
+ priority: 8,
2824
+ skeleton: `defineRule({
2825
+ id: '${ruleId}',
2826
+ description: '${finding.description}',
2827
+ eventTypes: ['TODO_EVENT_TYPE'],
2828
+ impl: (state, events) => {
2829
+ // TODO: Implement logic for "${finding.expectationName}"
2830
+ return RuleResult.noop('Not implemented');
2831
+ },
2832
+ contract: defineContract({
2833
+ ruleId: '${ruleId}',
2834
+ behavior: '${finding.expectationName}',
2835
+ examples: [
2836
+ { given: 'TODO', when: 'TODO', then: 'TODO' },
2837
+ ],
2838
+ invariants: [],
2839
+ }),
2840
+ });`
2841
+ };
2842
+ case "partial-coverage":
2843
+ return {
2844
+ findingType: "gap",
2845
+ entityId: finding.expectationName,
2846
+ message: `Expectation "${finding.expectationName}" is partially covered by rules [${finding.partialCoverage.join(", ")}]. Add contract examples or additional rules for uncovered conditions.`,
2847
+ action: "modify",
2848
+ priority: 6
2849
+ };
2850
+ case "no-contract":
2851
+ return {
2852
+ findingType: "gap",
2853
+ entityId: finding.expectationName,
2854
+ message: `Rules related to "${finding.expectationName}" ([${finding.partialCoverage.join(", ")}]) lack contracts. Add contracts with examples covering the expected behavior.`,
2855
+ action: "add-contract",
2856
+ priority: 7
2857
+ };
2858
+ default:
2859
+ return {
2860
+ findingType: "gap",
2861
+ entityId: finding.expectationName,
2862
+ message: finding.description,
2863
+ action: "add-rule",
2864
+ priority: 5
2865
+ };
2866
+ }
2867
+ }
2868
+ function suggestForContradiction(finding) {
2869
+ return {
2870
+ findingType: "contradiction",
2871
+ entityId: `${finding.ruleA}\u2194${finding.ruleB}`,
2872
+ message: `Rules "${finding.ruleA}" and "${finding.ruleB}" both produce fact "${finding.conflictingTag}" with potentially different payloads. Add priority ordering, merge the rules, or add distinguishing conditions.`,
2873
+ action: "add-priority",
2874
+ priority: 9,
2875
+ skeleton: `// Option 1: Add priority via meta
2876
+ defineRule({
2877
+ id: '${finding.ruleA}',
2878
+ meta: { priority: 10 }, // Higher priority wins
2879
+ // ...
2880
+ });
2881
+
2882
+ // Option 2: Add distinguishing conditions
2883
+ defineRule({
2884
+ id: '${finding.ruleA}',
2885
+ impl: (state, events) => {
2886
+ // Add condition to distinguish from "${finding.ruleB}"
2887
+ if (/* specific condition */) {
2888
+ return RuleResult.emit([{ tag: '${finding.conflictingTag}', payload: { /* ... */ } }]);
2889
+ }
2890
+ return RuleResult.skip('Deferred to ${finding.ruleB}');
2891
+ },
2892
+ });`
2893
+ };
2894
+ }
2895
+ function suggestForUnreachableState(finding) {
2896
+ const tags = finding.factTags.join(", ");
2897
+ return {
2898
+ findingType: "unreachable-state",
2899
+ entityId: finding.factTags.join("+"),
2900
+ message: `No rule produces facts [${tags}] together. If this state combination is valid, add a composite rule that produces all required facts.`,
2901
+ action: "add-rule",
2902
+ priority: 4,
2903
+ skeleton: `defineRule({
2904
+ id: 'composite-${finding.factTags[0]?.replace(".", "-") ?? "unknown"}',
2905
+ description: 'Produces facts [${tags}] together',
2906
+ impl: (state, events) => {
2907
+ return RuleResult.emit([
2908
+ ${finding.factTags.map((t) => ` { tag: '${t}', payload: {} },`).join("\n")}
2909
+ ]);
2910
+ },
2911
+ });`
2912
+ };
2913
+ }
2914
+ function suggestForShadowedRule(finding) {
2915
+ return {
2916
+ findingType: "shadowed-rule",
2917
+ entityId: finding.ruleId,
2918
+ message: `Rule "${finding.ruleId}" is always superseded by "${finding.shadowedBy}" for event types [${finding.sharedEventTypes.join(", ")}]. Consider merging the rules or adding a distinguishing condition to "${finding.ruleId}".`,
2919
+ action: "merge",
2920
+ priority: 3,
2921
+ skeleton: `// Option 1: Remove the shadowed rule
2922
+ registry.removeRule('${finding.ruleId}');
2923
+
2924
+ // Option 2: Add unique behavior to the shadowed rule
2925
+ defineRule({
2926
+ id: '${finding.ruleId}',
2927
+ impl: (state, events) => {
2928
+ // Add condition that "${finding.shadowedBy}" doesn't cover
2929
+ if (/* unique condition */) {
2930
+ return RuleResult.emit([/* unique facts */]);
2931
+ }
2932
+ return RuleResult.skip('Handled by ${finding.shadowedBy}');
2933
+ },
2934
+ });`
2935
+ };
2936
+ }
2937
+ function suggestForContractGap(finding) {
2938
+ let message;
2939
+ let action = "add-contract";
2940
+ let priority;
2941
+ switch (finding.type) {
2942
+ case "missing-error-path":
2943
+ message = `Rule "${finding.ruleId}" has no error/failure examples in its contract. Add examples showing what happens when preconditions fail, inputs are invalid, or the rule needs to skip.`;
2944
+ priority = 6;
2945
+ break;
2946
+ case "missing-edge-case":
2947
+ message = `Rule "${finding.ruleId}": ${finding.description}. Add contract examples covering all declared event types.`;
2948
+ priority = 5;
2949
+ break;
2950
+ case "missing-boundary":
2951
+ message = `Rule "${finding.ruleId}" has only 1 contract example. Add boundary condition examples (empty input, maximum values, concurrent events).`;
2952
+ priority = 4;
2953
+ break;
2954
+ case "cross-reference-broken":
2955
+ message = `Rule "${finding.ruleId}": ${finding.description}. Verify the referenced fact producer exists.`;
2956
+ action = "modify";
2957
+ priority = 7;
2958
+ break;
2959
+ default:
2960
+ message = finding.description;
2961
+ priority = 3;
2962
+ }
2963
+ return {
2964
+ findingType: "contract-gap",
2965
+ entityId: finding.ruleId,
2966
+ message,
2967
+ action,
2968
+ priority
2969
+ };
2970
+ }
2971
+
2972
+ // src/decision-ledger/report.ts
2973
+ function generateLedger(registry, engine, expectations) {
2974
+ const allEventTypes = /* @__PURE__ */ new Set();
2975
+ for (const rule of registry.getAllRules()) {
2976
+ if (rule.eventTypes) {
2977
+ const types = Array.isArray(rule.eventTypes) ? rule.eventTypes : [rule.eventTypes];
2978
+ for (const t of types) allEventTypes.add(t);
2979
+ }
2980
+ }
2981
+ const deadRules = findDeadRules(registry, [...allEventTypes]);
2982
+ const unreachableStates = findUnreachableStates(registry);
2983
+ const shadowedRules = findShadowedRules(registry);
2984
+ const contradictions = findContradictions(registry);
2985
+ const contractGaps = findContractGaps(registry);
2986
+ const gaps = expectations ? findGaps(registry, expectations) : [];
2987
+ const currentFacts = engine.getFacts();
2988
+ const factDerivationChains = currentFacts.map(
2989
+ (fact2) => traceDerivation(fact2.tag, engine, registry)
2990
+ );
2991
+ const suggestions = suggestAll({
2992
+ deadRules,
2993
+ gaps,
2994
+ contradictions,
2995
+ unreachableStates,
2996
+ shadowedRules,
2997
+ contractGaps
2998
+ });
2999
+ const totalRules = registry.getRuleIds().length;
3000
+ const totalConstraints = registry.getConstraintIds().length;
3001
+ const totalIssues = deadRules.length + unreachableStates.length + shadowedRules.length + contradictions.length + gaps.length;
3002
+ const maxIssues = Math.max(totalRules + totalConstraints, 1);
3003
+ const healthScore = Math.max(0, Math.round(100 - totalIssues / maxIssues * 100));
3004
+ return {
3005
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3006
+ factDerivationChains,
3007
+ deadRules,
3008
+ unreachableStates,
3009
+ shadowedRules,
3010
+ contradictions,
3011
+ gaps,
3012
+ suggestions,
3013
+ summary: {
3014
+ totalRules,
3015
+ totalConstraints,
3016
+ deadRuleCount: deadRules.length,
3017
+ unreachableStateCount: unreachableStates.length,
3018
+ shadowedRuleCount: shadowedRules.length,
3019
+ contradictionCount: contradictions.length,
3020
+ gapCount: gaps.length,
3021
+ suggestionCount: suggestions.length,
3022
+ healthScore
3023
+ }
3024
+ };
3025
+ }
3026
+ function formatLedger(report) {
3027
+ const lines = [];
3028
+ const icon = report.summary.healthScore >= 90 ? "\u2705" : report.summary.healthScore >= 70 ? "\u{1F7E1}" : "\u{1F534}";
3029
+ lines.push(`# ${icon} Decision Ledger Analysis`);
3030
+ lines.push("");
3031
+ lines.push(`**Health Score:** ${report.summary.healthScore}/100`);
3032
+ lines.push(`**Timestamp:** ${report.timestamp}`);
3033
+ lines.push("");
3034
+ lines.push("## Summary");
3035
+ lines.push("");
3036
+ lines.push(`| Metric | Count |`);
3037
+ lines.push(`|--------|-------|`);
3038
+ lines.push(`| Rules | ${report.summary.totalRules} |`);
3039
+ lines.push(`| Constraints | ${report.summary.totalConstraints} |`);
3040
+ lines.push(`| Dead Rules | ${report.summary.deadRuleCount} |`);
3041
+ lines.push(`| Unreachable States | ${report.summary.unreachableStateCount} |`);
3042
+ lines.push(`| Shadowed Rules | ${report.summary.shadowedRuleCount} |`);
3043
+ lines.push(`| Contradictions | ${report.summary.contradictionCount} |`);
3044
+ lines.push(`| Gaps | ${report.summary.gapCount} |`);
3045
+ lines.push(`| Suggestions | ${report.summary.suggestionCount} |`);
3046
+ lines.push("");
3047
+ if (report.deadRules.length > 0) {
3048
+ lines.push("## \u{1F480} Dead Rules");
3049
+ lines.push("");
3050
+ for (const dr of report.deadRules) {
3051
+ lines.push(`- **${dr.ruleId}**: ${dr.reason}`);
3052
+ }
3053
+ lines.push("");
3054
+ }
3055
+ if (report.unreachableStates.length > 0) {
3056
+ lines.push("## \u{1F6AB} Unreachable States");
3057
+ lines.push("");
3058
+ for (const us of report.unreachableStates) {
3059
+ lines.push(`- **[${us.factTags.join(", ")}]**: ${us.reason}`);
3060
+ }
3061
+ lines.push("");
3062
+ }
3063
+ if (report.shadowedRules.length > 0) {
3064
+ lines.push("## \u{1F47B} Shadowed Rules");
3065
+ lines.push("");
3066
+ for (const sr of report.shadowedRules) {
3067
+ lines.push(`- **${sr.ruleId}** shadowed by **${sr.shadowedBy}**: ${sr.reason}`);
3068
+ }
3069
+ lines.push("");
3070
+ }
3071
+ if (report.contradictions.length > 0) {
3072
+ lines.push("## \u26A1 Contradictions");
3073
+ lines.push("");
3074
+ for (const c of report.contradictions) {
3075
+ lines.push(`- **${c.ruleA}** \u2194 **${c.ruleB}** on \`${c.conflictingTag}\`: ${c.reason}`);
3076
+ }
3077
+ lines.push("");
3078
+ }
3079
+ if (report.gaps.length > 0) {
3080
+ lines.push("## \u{1F573}\uFE0F Gaps");
3081
+ lines.push("");
3082
+ for (const g of report.gaps) {
3083
+ lines.push(`- **${g.expectationName}** (${g.type}): ${g.description}`);
3084
+ }
3085
+ lines.push("");
3086
+ }
3087
+ if (report.factDerivationChains.length > 0) {
3088
+ lines.push("## \u{1F517} Fact Derivation Chains");
3089
+ lines.push("");
3090
+ for (const chain of report.factDerivationChains) {
3091
+ if (chain.steps.length === 0) continue;
3092
+ lines.push(`### \`${chain.targetFact}\` (depth: ${chain.depth})`);
3093
+ for (const step of chain.steps) {
3094
+ const icon2 = step.type === "event" ? "\u26A1" : step.type === "rule-fired" ? "\u2699\uFE0F" : step.type === "fact-produced" ? "\u{1F4E6}" : "\u{1F4D6}";
3095
+ lines.push(` ${icon2} ${step.description}`);
3096
+ }
3097
+ lines.push("");
3098
+ }
3099
+ }
3100
+ if (report.suggestions.length > 0) {
3101
+ lines.push("## \u{1F4A1} Suggestions");
3102
+ lines.push("");
3103
+ for (const s of report.suggestions) {
3104
+ const priorityIcon = s.priority >= 8 ? "\u{1F534}" : s.priority >= 5 ? "\u{1F7E1}" : "\u{1F7E2}";
3105
+ lines.push(`${priorityIcon} **[${s.findingType}]** ${s.message}`);
3106
+ if (s.skeleton) {
3107
+ lines.push("```typescript");
3108
+ lines.push(s.skeleton);
3109
+ lines.push("```");
3110
+ }
3111
+ lines.push("");
3112
+ }
3113
+ }
3114
+ return lines.join("\n");
3115
+ }
3116
+ function formatBuildOutput(report) {
3117
+ const lines = [];
3118
+ lines.push(`::group::Decision Ledger Analysis (Score: ${report.summary.healthScore}/100)`);
3119
+ for (const c of report.contradictions) {
3120
+ lines.push(`::error title=Contradiction::Rules "${c.ruleA}" and "${c.ruleB}" both produce "${c.conflictingTag}"`);
3121
+ }
3122
+ for (const g of report.gaps) {
3123
+ if (g.type === "no-rule") {
3124
+ lines.push(`::error title=Missing Rule::No rule covers expectation "${g.expectationName}"`);
3125
+ }
3126
+ }
3127
+ for (const dr of report.deadRules) {
3128
+ lines.push(`::warning title=Dead Rule::Rule "${dr.ruleId}" can never fire (requires [${dr.requiredEventTypes.join(", ")}])`);
3129
+ }
3130
+ for (const sr of report.shadowedRules) {
3131
+ lines.push(`::warning title=Shadowed Rule::Rule "${sr.ruleId}" is always superseded by "${sr.shadowedBy}"`);
3132
+ }
3133
+ for (const us of report.unreachableStates) {
3134
+ lines.push(`::warning title=Unreachable State::Facts [${us.factTags.join(", ")}] cannot be produced together`);
3135
+ }
3136
+ for (const g of report.gaps) {
3137
+ if (g.type === "partial-coverage") {
3138
+ lines.push(`::warning title=Partial Coverage::Expectation "${g.expectationName}" is only partially covered`);
3139
+ } else if (g.type === "no-contract") {
3140
+ lines.push(`::warning title=Missing Contract::Rules for "${g.expectationName}" lack contracts`);
3141
+ }
3142
+ }
3143
+ lines.push("");
3144
+ lines.push(`Rules: ${report.summary.totalRules} | Constraints: ${report.summary.totalConstraints} | Health: ${report.summary.healthScore}/100`);
3145
+ lines.push(`Dead: ${report.summary.deadRuleCount} | Unreachable: ${report.summary.unreachableStateCount} | Shadowed: ${report.summary.shadowedRuleCount} | Contradictions: ${report.summary.contradictionCount} | Gaps: ${report.summary.gapCount}`);
3146
+ lines.push("::endgroup::");
3147
+ return lines.join("\n");
3148
+ }
3149
+ function diffLedgers(before, after) {
3150
+ const changes = [];
3151
+ diffItems(
3152
+ before.deadRules,
3153
+ after.deadRules,
3154
+ (dr) => dr.ruleId,
3155
+ (dr) => `Dead rule: ${dr.ruleId} \u2014 ${dr.reason}`,
3156
+ "dead-rule",
3157
+ changes
3158
+ );
3159
+ diffItems(
3160
+ before.unreachableStates,
3161
+ after.unreachableStates,
3162
+ (us) => us.factTags.join("+"),
3163
+ (us) => `Unreachable state: [${us.factTags.join(", ")}]`,
3164
+ "unreachable-state",
3165
+ changes
3166
+ );
3167
+ diffItems(
3168
+ before.shadowedRules,
3169
+ after.shadowedRules,
3170
+ (sr) => sr.ruleId,
3171
+ (sr) => `Shadowed rule: ${sr.ruleId} by ${sr.shadowedBy}`,
3172
+ "shadowed-rule",
3173
+ changes
3174
+ );
3175
+ diffItems(
3176
+ before.contradictions,
3177
+ after.contradictions,
3178
+ (c) => `${c.ruleA}\u2194${c.ruleB}`,
3179
+ (c) => `Contradiction: ${c.ruleA} \u2194 ${c.ruleB} on ${c.conflictingTag}`,
3180
+ "contradiction",
3181
+ changes
3182
+ );
3183
+ diffItems(
3184
+ before.gaps,
3185
+ after.gaps,
3186
+ (g) => g.expectationName,
3187
+ (g) => `Gap: ${g.expectationName} (${g.type})`,
3188
+ "gap",
3189
+ changes
3190
+ );
3191
+ const scoreDelta = after.summary.healthScore - before.summary.healthScore;
3192
+ const scoreDirection = scoreDelta > 0 ? "improved" : scoreDelta < 0 ? "degraded" : "unchanged";
3193
+ return {
3194
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3195
+ beforeTimestamp: before.timestamp,
3196
+ afterTimestamp: after.timestamp,
3197
+ changes,
3198
+ scoreDelta,
3199
+ summary: `Score ${scoreDirection} by ${Math.abs(scoreDelta)} points (${before.summary.healthScore} \u2192 ${after.summary.healthScore}). ${changes.length} changes: ${changes.filter((c) => c.type === "added").length} added, ${changes.filter((c) => c.type === "removed").length} removed, ${changes.filter((c) => c.type === "changed").length} changed.`
3200
+ };
3201
+ }
3202
+ function diffItems(before, after, getKey, describe, category, changes) {
3203
+ const beforeKeys = new Set(before.map(getKey));
3204
+ const afterKeys = new Set(after.map(getKey));
3205
+ for (const item of after) {
3206
+ const key = getKey(item);
3207
+ if (!beforeKeys.has(key)) {
3208
+ changes.push({
3209
+ type: "added",
3210
+ category,
3211
+ description: describe(item),
3212
+ entityId: key
3213
+ });
3214
+ }
3215
+ }
3216
+ for (const item of before) {
3217
+ const key = getKey(item);
3218
+ if (!afterKeys.has(key)) {
3219
+ changes.push({
3220
+ type: "removed",
3221
+ category,
3222
+ description: describe(item),
3223
+ entityId: key
3224
+ });
3225
+ }
3226
+ }
3227
+ }
3228
+
2015
3229
  // src/runtime/terminal-adapter.ts
2016
3230
  async function defaultExecutor(command, options) {
2017
3231
  try {
@@ -6193,6 +7407,509 @@ function expectationGate(config = {}) {
6193
7407
  onViolation: "expectations-failed"
6194
7408
  });
6195
7409
  }
7410
+
7411
+ // src/chronos/project-chronicle.ts
7412
+ var ProjectChronicle = class {
7413
+ events = [];
7414
+ maxEvents;
7415
+ now;
7416
+ constructor(options = {}) {
7417
+ this.maxEvents = options.maxEvents ?? 1e4;
7418
+ this.now = options.now ?? (() => Date.now());
7419
+ }
7420
+ // ── Recording ───────────────────────────────────────────────────────────
7421
+ /**
7422
+ * Record a project event. Returns the recorded event (with timestamp filled in).
7423
+ */
7424
+ record(event) {
7425
+ const full = {
7426
+ kind: event.kind,
7427
+ action: event.action,
7428
+ subject: event.subject,
7429
+ timestamp: event.timestamp ?? this.now(),
7430
+ metadata: event.metadata,
7431
+ diff: event.diff
7432
+ };
7433
+ this.events.push(full);
7434
+ if (this.maxEvents > 0 && this.events.length > this.maxEvents) {
7435
+ this.events = this.events.slice(this.events.length - this.maxEvents);
7436
+ }
7437
+ return full;
7438
+ }
7439
+ // ── Convenience recorders ─────────────────────────────────────────────
7440
+ recordRuleRegistered(ruleId, meta = {}) {
7441
+ return this.record({ kind: "rule", action: "registered", subject: ruleId, metadata: meta });
7442
+ }
7443
+ recordRuleModified(ruleId, diff, meta = {}) {
7444
+ return this.record({ kind: "rule", action: "modified", subject: ruleId, metadata: meta, diff });
7445
+ }
7446
+ recordRuleRemoved(ruleId, meta = {}) {
7447
+ return this.record({ kind: "rule", action: "removed", subject: ruleId, metadata: meta });
7448
+ }
7449
+ recordContractAdded(contractId, meta = {}) {
7450
+ return this.record({ kind: "contract", action: "added", subject: contractId, metadata: meta });
7451
+ }
7452
+ recordContractModified(contractId, diff, meta = {}) {
7453
+ return this.record({ kind: "contract", action: "modified", subject: contractId, metadata: meta, diff });
7454
+ }
7455
+ recordExpectationSatisfied(name, meta = {}) {
7456
+ return this.record({ kind: "expectation", action: "satisfied", subject: name, metadata: meta });
7457
+ }
7458
+ recordExpectationViolated(name, meta = {}) {
7459
+ return this.record({ kind: "expectation", action: "violated", subject: name, metadata: meta });
7460
+ }
7461
+ recordGateTransition(gateName, from, to, meta = {}) {
7462
+ return this.record({
7463
+ kind: "gate",
7464
+ action: to,
7465
+ subject: gateName,
7466
+ metadata: { ...meta, from, to },
7467
+ diff: { before: from, after: to }
7468
+ });
7469
+ }
7470
+ recordBuildAudit(score, delta, meta = {}) {
7471
+ return this.record({
7472
+ kind: "build",
7473
+ action: "audit-complete",
7474
+ subject: "completeness",
7475
+ metadata: { ...meta, score, delta }
7476
+ });
7477
+ }
7478
+ recordFactIntroduced(factTag, meta = {}) {
7479
+ return this.record({ kind: "fact", action: "introduced", subject: factTag, metadata: meta });
7480
+ }
7481
+ recordFactDeprecated(factTag, meta = {}) {
7482
+ return this.record({ kind: "fact", action: "deprecated", subject: factTag, metadata: meta });
7483
+ }
7484
+ // ── Access ──────────────────────────────────────────────────────────────
7485
+ /** Return a shallow copy of all events. */
7486
+ getEvents() {
7487
+ return [...this.events];
7488
+ }
7489
+ /** Total number of recorded events. */
7490
+ get size() {
7491
+ return this.events.length;
7492
+ }
7493
+ /** Clear all events (primarily for testing). */
7494
+ clear() {
7495
+ this.events = [];
7496
+ }
7497
+ };
7498
+ function createProjectChronicle(options) {
7499
+ return new ProjectChronicle(options);
7500
+ }
7501
+
7502
+ // src/chronos/timeline.ts
7503
+ var Timeline = class {
7504
+ constructor(chronicle) {
7505
+ this.chronicle = chronicle;
7506
+ }
7507
+ // ── Queries ─────────────────────────────────────────────────────────────
7508
+ /**
7509
+ * Query events with optional filtering.
7510
+ * Returns matching events sorted chronologically (oldest first).
7511
+ */
7512
+ getTimeline(filter) {
7513
+ let events = this.chronicle.getEvents();
7514
+ if (!filter) return events;
7515
+ events = applyFilter(events, filter);
7516
+ return events;
7517
+ }
7518
+ /**
7519
+ * Get all events since a timestamp (inclusive).
7520
+ */
7521
+ getEventsSince(timestamp) {
7522
+ return this.getTimeline({ since: timestamp });
7523
+ }
7524
+ /**
7525
+ * Compute a behavioral delta between two timestamps.
7526
+ */
7527
+ getDelta(from, to) {
7528
+ const events = this.getTimeline({ since: from, until: to });
7529
+ return buildDelta(from, to, events);
7530
+ }
7531
+ /**
7532
+ * Get full history for a specific subject (rule id, gate name, etc.).
7533
+ * Sorted chronologically.
7534
+ */
7535
+ getHistory(subjectId) {
7536
+ return this.getTimeline({ subject: subjectId });
7537
+ }
7538
+ };
7539
+ function applyFilter(events, filter) {
7540
+ return events.filter((e) => {
7541
+ if (filter.kind) {
7542
+ const kinds = Array.isArray(filter.kind) ? filter.kind : [filter.kind];
7543
+ if (!kinds.includes(e.kind)) return false;
7544
+ }
7545
+ if (filter.action) {
7546
+ const actions = Array.isArray(filter.action) ? filter.action : [filter.action];
7547
+ if (!actions.includes(e.action)) return false;
7548
+ }
7549
+ if (filter.subject) {
7550
+ const subjects = Array.isArray(filter.subject) ? filter.subject : [filter.subject];
7551
+ if (!subjects.includes(e.subject)) return false;
7552
+ }
7553
+ if (filter.since != null && e.timestamp < filter.since) return false;
7554
+ if (filter.until != null && e.timestamp > filter.until) return false;
7555
+ return true;
7556
+ });
7557
+ }
7558
+ var ADD_ACTIONS = /* @__PURE__ */ new Set(["registered", "added", "introduced", "opened"]);
7559
+ var REMOVE_ACTIONS = /* @__PURE__ */ new Set(["removed", "deprecated", "closed"]);
7560
+ var MODIFY_ACTIONS = /* @__PURE__ */ new Set(["modified", "updated"]);
7561
+ function buildDelta(from, to, events) {
7562
+ const summary = {};
7563
+ const added = /* @__PURE__ */ new Set();
7564
+ const removed = /* @__PURE__ */ new Set();
7565
+ const modified = /* @__PURE__ */ new Set();
7566
+ for (const e of events) {
7567
+ summary[e.kind] = (summary[e.kind] ?? 0) + 1;
7568
+ if (ADD_ACTIONS.has(e.action)) {
7569
+ added.add(e.subject);
7570
+ removed.delete(e.subject);
7571
+ } else if (REMOVE_ACTIONS.has(e.action)) {
7572
+ removed.add(e.subject);
7573
+ added.delete(e.subject);
7574
+ } else if (MODIFY_ACTIONS.has(e.action)) {
7575
+ modified.add(e.subject);
7576
+ }
7577
+ }
7578
+ return {
7579
+ from,
7580
+ to,
7581
+ events,
7582
+ summary,
7583
+ added: [...added],
7584
+ removed: [...removed],
7585
+ modified: [...modified]
7586
+ };
7587
+ }
7588
+ function createTimeline(chronicle) {
7589
+ return new Timeline(chronicle);
7590
+ }
7591
+
7592
+ // src/chronos/hooks.ts
7593
+ function enableProjectChronicle(registry, engine, options = {}) {
7594
+ const chronicle = options.chronicle ?? createProjectChronicle();
7595
+ const recordSteps = options.recordSteps ?? true;
7596
+ const recordConstraints = options.recordConstraints ?? true;
7597
+ const origRegisterRule = registry.registerRule.bind(registry);
7598
+ const origRegisterModule = registry.registerModule.bind(registry);
7599
+ const origStep = engine.step.bind(engine);
7600
+ const origCheckConstraints = engine.checkConstraints.bind(engine);
7601
+ registry.registerRule = function hookedRegisterRule(descriptor) {
7602
+ origRegisterRule(descriptor);
7603
+ chronicle.recordRuleRegistered(descriptor.id, {
7604
+ description: descriptor.description,
7605
+ hasContract: !!descriptor.contract,
7606
+ eventTypes: descriptor.eventTypes
7607
+ });
7608
+ if (descriptor.contract) {
7609
+ chronicle.recordContractAdded(descriptor.id, {
7610
+ behavior: descriptor.contract.behavior,
7611
+ examplesCount: descriptor.contract.examples?.length ?? 0,
7612
+ invariantsCount: descriptor.contract.invariants?.length ?? 0
7613
+ });
7614
+ }
7615
+ };
7616
+ registry.registerModule = function hookedRegisterModule(module2) {
7617
+ origRegisterModule(module2);
7618
+ for (const rule of module2.rules) {
7619
+ chronicle.recordRuleRegistered(rule.id, {
7620
+ description: rule.description,
7621
+ hasContract: !!rule.contract,
7622
+ eventTypes: rule.eventTypes,
7623
+ registeredVia: "module"
7624
+ });
7625
+ if (rule.contract) {
7626
+ chronicle.recordContractAdded(rule.id, {
7627
+ behavior: rule.contract.behavior,
7628
+ examplesCount: rule.contract.examples?.length ?? 0,
7629
+ invariantsCount: rule.contract.invariants?.length ?? 0
7630
+ });
7631
+ }
7632
+ }
7633
+ };
7634
+ if (recordSteps) {
7635
+ engine.step = function hookedStep(events) {
7636
+ const result = origStep(events);
7637
+ const factTags = /* @__PURE__ */ new Set();
7638
+ for (const fact2 of result.state.facts) {
7639
+ factTags.add(fact2.tag);
7640
+ }
7641
+ chronicle.record({
7642
+ kind: "build",
7643
+ action: "step-complete",
7644
+ subject: "engine",
7645
+ metadata: {
7646
+ eventsProcessed: events.length,
7647
+ eventTags: events.map((e) => e.tag),
7648
+ factsAfter: result.state.facts.length,
7649
+ diagnosticsCount: result.diagnostics.length
7650
+ }
7651
+ });
7652
+ for (const diag of result.diagnostics) {
7653
+ if (diag.kind === "constraint-violation") {
7654
+ chronicle.record({
7655
+ kind: "expectation",
7656
+ action: "violated",
7657
+ subject: extractSubjectFromDiag(diag),
7658
+ metadata: { message: diag.message, data: diag.data }
7659
+ });
7660
+ }
7661
+ }
7662
+ return result;
7663
+ };
7664
+ }
7665
+ if (recordConstraints) {
7666
+ engine.checkConstraints = function hookedCheckConstraints() {
7667
+ const diagnostics = origCheckConstraints();
7668
+ chronicle.record({
7669
+ kind: "build",
7670
+ action: "constraints-checked",
7671
+ subject: "engine",
7672
+ metadata: {
7673
+ violations: diagnostics.length
7674
+ }
7675
+ });
7676
+ for (const diag of diagnostics) {
7677
+ chronicle.record({
7678
+ kind: "expectation",
7679
+ action: "violated",
7680
+ subject: extractSubjectFromDiag(diag),
7681
+ metadata: { message: diag.message }
7682
+ });
7683
+ }
7684
+ return diagnostics;
7685
+ };
7686
+ }
7687
+ function disconnect() {
7688
+ registry.registerRule = origRegisterRule;
7689
+ registry.registerModule = origRegisterModule;
7690
+ engine.step = origStep;
7691
+ engine.checkConstraints = origCheckConstraints;
7692
+ }
7693
+ return { chronicle, disconnect };
7694
+ }
7695
+ function recordAudit(chronicle, report, previousScore) {
7696
+ const delta = previousScore != null ? report.score - previousScore : 0;
7697
+ chronicle.recordBuildAudit(report.score, delta, {
7698
+ rating: report.rating,
7699
+ rulesCovered: report.rules.covered,
7700
+ rulesTotal: report.rules.total,
7701
+ constraintsCovered: report.constraints.covered,
7702
+ constraintsTotal: report.constraints.total,
7703
+ contractsCovered: report.contracts.withContracts,
7704
+ contractsTotal: report.contracts.total
7705
+ });
7706
+ }
7707
+ function extractSubjectFromDiag(diag) {
7708
+ const data = diag.data;
7709
+ if (data?.constraintId && typeof data.constraintId === "string") return data.constraintId;
7710
+ if (data?.ruleId && typeof data.ruleId === "string") return data.ruleId;
7711
+ return "unknown";
7712
+ }
7713
+
7714
+ // src/chronos/diff.ts
7715
+ function diffRegistries(before, after) {
7716
+ const beforeRules = toIdMap(before.rules);
7717
+ const afterRules = toIdMap(after.rules);
7718
+ const beforeConstraints = toIdMap(before.constraints);
7719
+ const afterConstraints = toIdMap(after.constraints);
7720
+ return {
7721
+ rulesAdded: setDiff(afterRules, beforeRules),
7722
+ rulesRemoved: setDiff(beforeRules, afterRules),
7723
+ rulesModified: findModified(beforeRules, afterRules),
7724
+ constraintsAdded: setDiff(afterConstraints, beforeConstraints),
7725
+ constraintsRemoved: setDiff(beforeConstraints, afterConstraints),
7726
+ constraintsModified: findModified(beforeConstraints, afterConstraints)
7727
+ };
7728
+ }
7729
+ function diffContracts(before, after) {
7730
+ const beforeMap = toRecord(before.coverage);
7731
+ const afterMap = toRecord(after.coverage);
7732
+ const contractsAdded = [];
7733
+ const contractsRemoved = [];
7734
+ for (const [id, has] of Object.entries(afterMap)) {
7735
+ if (has && !beforeMap[id]) {
7736
+ contractsAdded.push(id);
7737
+ }
7738
+ }
7739
+ for (const [id, had] of Object.entries(beforeMap)) {
7740
+ if (had && !afterMap[id]) {
7741
+ contractsRemoved.push(id);
7742
+ }
7743
+ }
7744
+ const countTrue = (r) => Object.values(r).filter(Boolean).length;
7745
+ const totalBefore = Object.keys(beforeMap).length;
7746
+ const totalAfter = Object.keys(afterMap).length;
7747
+ return {
7748
+ contractsAdded,
7749
+ contractsRemoved,
7750
+ coverageBefore: totalBefore > 0 ? countTrue(beforeMap) / totalBefore : 0,
7751
+ coverageAfter: totalAfter > 0 ? countTrue(afterMap) / totalAfter : 0
7752
+ };
7753
+ }
7754
+ function diffExpectations(before, after) {
7755
+ const beforeMap = toRecord(before.expectations);
7756
+ const afterMap = toRecord(after.expectations);
7757
+ const allKeys = /* @__PURE__ */ new Set([...Object.keys(beforeMap), ...Object.keys(afterMap)]);
7758
+ const newlySatisfied = [];
7759
+ const newlyViolated = [];
7760
+ const unchanged = [];
7761
+ for (const key of allKeys) {
7762
+ const was = beforeMap[key] ?? false;
7763
+ const is = afterMap[key] ?? false;
7764
+ if (!was && is) {
7765
+ newlySatisfied.push(key);
7766
+ } else if (was && !is) {
7767
+ newlyViolated.push(key);
7768
+ } else {
7769
+ unchanged.push(key);
7770
+ }
7771
+ }
7772
+ return { newlySatisfied, newlyViolated, unchanged };
7773
+ }
7774
+ function formatDelta(diff) {
7775
+ const lines = [];
7776
+ if (diff.rulesAdded.length > 0)
7777
+ lines.push(`+ Rules added: ${diff.rulesAdded.join(", ")}`);
7778
+ if (diff.rulesRemoved.length > 0)
7779
+ lines.push(`- Rules removed: ${diff.rulesRemoved.join(", ")}`);
7780
+ if (diff.rulesModified.length > 0)
7781
+ lines.push(`~ Rules modified: ${diff.rulesModified.join(", ")}`);
7782
+ if (diff.constraintsAdded.length > 0)
7783
+ lines.push(`+ Constraints added: ${diff.constraintsAdded.join(", ")}`);
7784
+ if (diff.constraintsRemoved.length > 0)
7785
+ lines.push(`- Constraints removed: ${diff.constraintsRemoved.join(", ")}`);
7786
+ if (diff.constraintsModified.length > 0)
7787
+ lines.push(`~ Constraints modified: ${diff.constraintsModified.join(", ")}`);
7788
+ return lines.length > 0 ? lines.join("\n") : "No behavioral changes.";
7789
+ }
7790
+ function formatCommitMessage(diff) {
7791
+ const praxisDiff = {
7792
+ rulesAdded: diff.rulesAdded,
7793
+ rulesRemoved: diff.rulesRemoved,
7794
+ rulesModified: diff.rulesModified,
7795
+ contractsAdded: [],
7796
+ contractsRemoved: [],
7797
+ expectationsAdded: diff.constraintsAdded,
7798
+ expectationsRemoved: diff.constraintsRemoved,
7799
+ gateChanges: []
7800
+ };
7801
+ return commitFromDiff(praxisDiff);
7802
+ }
7803
+ function formatReleaseNotes(diffs) {
7804
+ const sections = [];
7805
+ const allAdded = [];
7806
+ const allRemoved = [];
7807
+ const allModified = [];
7808
+ for (const diff of diffs) {
7809
+ allAdded.push(...diff.rulesAdded, ...diff.constraintsAdded);
7810
+ allRemoved.push(...diff.rulesRemoved, ...diff.constraintsRemoved);
7811
+ allModified.push(...diff.rulesModified, ...diff.constraintsModified);
7812
+ }
7813
+ const added = [...new Set(allAdded)];
7814
+ const removed = [...new Set(allRemoved)];
7815
+ const modified = [...new Set(allModified)];
7816
+ if (added.length > 0) {
7817
+ sections.push(`### Added
7818
+ ${added.map((id) => `- ${id}`).join("\n")}`);
7819
+ }
7820
+ if (modified.length > 0) {
7821
+ sections.push(`### Changed
7822
+ ${modified.map((id) => `- ${id}`).join("\n")}`);
7823
+ }
7824
+ if (removed.length > 0) {
7825
+ sections.push(`### Removed
7826
+ ${removed.map((id) => `- ${id}`).join("\n")}`);
7827
+ }
7828
+ if (sections.length === 0) return "No behavioral changes in this release.";
7829
+ return `## Release Notes
7830
+
7831
+ ${sections.join("\n\n")}`;
7832
+ }
7833
+ function toIdMap(input) {
7834
+ if (input instanceof Map) return input;
7835
+ const map = /* @__PURE__ */ new Map();
7836
+ for (const item of input) map.set(item.id, item);
7837
+ return map;
7838
+ }
7839
+ function setDiff(a, b) {
7840
+ const result = [];
7841
+ for (const key of a.keys()) {
7842
+ if (!b.has(key)) result.push(key);
7843
+ }
7844
+ return result;
7845
+ }
7846
+ function findModified(before, after) {
7847
+ const result = [];
7848
+ for (const [key, beforeVal] of before) {
7849
+ const afterVal = after.get(key);
7850
+ if (afterVal && beforeVal.description !== afterVal.description) {
7851
+ result.push(key);
7852
+ }
7853
+ }
7854
+ return result;
7855
+ }
7856
+ function toRecord(input) {
7857
+ if (input instanceof Map) {
7858
+ const result = {};
7859
+ for (const [k, v] of input) result[k] = v;
7860
+ return result;
7861
+ }
7862
+ return input;
7863
+ }
7864
+ function commitFromDiff(diff) {
7865
+ const parts = [];
7866
+ const bodyParts = [];
7867
+ const totalAdded = diff.rulesAdded.length + diff.contractsAdded.length + diff.expectationsAdded.length;
7868
+ const totalRemoved = diff.rulesRemoved.length + diff.contractsRemoved.length + diff.expectationsRemoved.length;
7869
+ const totalModified = diff.rulesModified.length;
7870
+ if (totalAdded > 0 && totalRemoved === 0 && totalModified === 0) {
7871
+ if (diff.rulesAdded.length > 0) {
7872
+ const scope = inferScope2(diff.rulesAdded);
7873
+ parts.push(`feat(${scope}): add ${fmtIds(diff.rulesAdded)}`);
7874
+ } else if (diff.contractsAdded.length > 0) {
7875
+ parts.push(`feat(contracts): add contracts for ${fmtIds(diff.contractsAdded)}`);
7876
+ } else {
7877
+ parts.push(`feat(expectations): add ${fmtIds(diff.expectationsAdded)}`);
7878
+ }
7879
+ } else if (totalRemoved > 0 && totalAdded === 0) {
7880
+ if (diff.rulesRemoved.length > 0) {
7881
+ const scope = inferScope2(diff.rulesRemoved);
7882
+ parts.push(`refactor(${scope}): remove ${fmtIds(diff.rulesRemoved)}`);
7883
+ } else {
7884
+ parts.push(`refactor: remove ${totalRemoved} item(s)`);
7885
+ }
7886
+ } else if (totalModified > 0) {
7887
+ const scope = inferScope2(diff.rulesModified);
7888
+ parts.push(`refactor(${scope}): update ${fmtIds(diff.rulesModified)}`);
7889
+ } else {
7890
+ parts.push("chore: behavioral state update");
7891
+ }
7892
+ if (diff.rulesAdded.length > 0) bodyParts.push(`Rules added: ${diff.rulesAdded.join(", ")}`);
7893
+ if (diff.rulesRemoved.length > 0) bodyParts.push(`Rules removed: ${diff.rulesRemoved.join(", ")}`);
7894
+ if (diff.rulesModified.length > 0) bodyParts.push(`Rules modified: ${diff.rulesModified.join(", ")}`);
7895
+ const subject = parts[0] || "chore: update";
7896
+ return bodyParts.length > 0 ? `${subject}
7897
+
7898
+ ${bodyParts.join("\n")}` : subject;
7899
+ }
7900
+ function inferScope2(ids) {
7901
+ if (ids.length === 0) return "rules";
7902
+ const prefixes = ids.map((id) => {
7903
+ const slash = id.indexOf("/");
7904
+ return slash > 0 ? id.slice(0, slash) : id;
7905
+ });
7906
+ const unique = new Set(prefixes);
7907
+ return unique.size === 1 ? prefixes[0] : "rules";
7908
+ }
7909
+ function fmtIds(ids) {
7910
+ if (ids.length <= 3) return ids.join(", ");
7911
+ return `${ids.slice(0, 2).join(", ")} (+${ids.length - 2} more)`;
7912
+ }
6196
7913
  // Annotate the CommonJS export names for ESM import in node:
6197
7914
  0 && (module.exports = {
6198
7915
  AcknowledgeContractGap,
@@ -6218,12 +7935,15 @@ function expectationGate(config = {}) {
6218
7935
  PraxisDBStore,
6219
7936
  PraxisRegistry,
6220
7937
  PraxisSchemaRegistry,
7938
+ ProjectChronicle,
6221
7939
  ReactiveLogicEngine,
6222
7940
  RegistryIntrospector,
6223
7941
  RuleResult,
6224
7942
  StateDocsGenerator,
6225
7943
  TerminalAdapter,
7944
+ Timeline,
6226
7945
  ValidateContracts,
7946
+ analyzeDependencyGraph,
6227
7947
  attachAllIntegrations,
6228
7948
  attachTauriToEngine,
6229
7949
  attachToEngine,
@@ -6249,16 +7969,19 @@ function expectationGate(config = {}) {
6249
7969
  createPraxisDBStore,
6250
7970
  createPraxisEngine,
6251
7971
  createPraxisLocalFirst,
7972
+ createProjectChronicle,
6252
7973
  createReactiveEngine,
6253
7974
  createSchemaRegistry,
6254
7975
  createSchemaTemplate,
6255
7976
  createStateDocsGenerator,
6256
7977
  createTauriPraxisAdapter,
6257
7978
  createTerminalAdapter,
7979
+ createTimeline,
6258
7980
  createTimerActor,
6259
7981
  createUIModule,
6260
7982
  createUnifiedApp,
6261
7983
  createUnumAdapter,
7984
+ crossReferenceContracts,
6262
7985
  dataRules,
6263
7986
  defineConstraint,
6264
7987
  defineContract,
@@ -6267,17 +7990,33 @@ function expectationGate(config = {}) {
6267
7990
  defineGate,
6268
7991
  defineModule,
6269
7992
  defineRule,
7993
+ diffContracts,
7994
+ diffExpectations,
7995
+ diffLedgers,
7996
+ diffRegistries,
6270
7997
  dirtyGuardRule,
7998
+ enableProjectChronicle,
6271
7999
  errorDisplayRule,
6272
8000
  expectBehavior,
6273
8001
  expectationGate,
6274
8002
  fact,
6275
8003
  filterEvents,
6276
8004
  filterFacts,
8005
+ findContractGaps,
8006
+ findContradictions,
8007
+ findDeadRules,
6277
8008
  findEvent,
6278
8009
  findFact,
8010
+ findGaps,
8011
+ findShadowedRules,
8012
+ findUnreachableStates,
6279
8013
  formRules,
8014
+ formatBehavioralCommit,
8015
+ formatBuildOutput,
8016
+ formatDelta,
6280
8017
  formatGate,
8018
+ formatLedger,
8019
+ formatReleaseNotes,
6281
8020
  formatReport,
6282
8021
  formatValidationReport,
6283
8022
  formatValidationReportJSON,
@@ -6285,6 +8024,7 @@ function expectationGate(config = {}) {
6285
8024
  formatVerificationReport,
6286
8025
  generateDocs,
6287
8026
  generateId,
8027
+ generateLedger,
6288
8028
  generateTauriConfig,
6289
8029
  getContract,
6290
8030
  getEventPath,
@@ -6304,12 +8044,17 @@ function expectationGate(config = {}) {
6304
8044
  navigationRules,
6305
8045
  noInteractionWhileLoadingConstraint,
6306
8046
  offlineIndicatorRule,
8047
+ recordAudit,
6307
8048
  registerSchema,
6308
8049
  resizeEvent,
6309
8050
  runTerminalCommand,
6310
8051
  schemaToCanvas,
6311
8052
  semverContract,
8053
+ suggest,
8054
+ suggestAll,
6312
8055
  toastRules,
8056
+ traceDerivation,
8057
+ traceImpact,
6313
8058
  uiModule,
6314
8059
  uiStateChanged,
6315
8060
  validateContracts,
@@ -6317,5 +8062,7 @@ function expectationGate(config = {}) {
6317
8062
  validateSchema,
6318
8063
  validateWithGuardian,
6319
8064
  verify,
8065
+ verifyContractExamples,
8066
+ verifyInvariants,
6320
8067
  viewportRule
6321
8068
  });