@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
@@ -0,0 +1,282 @@
1
+ import {
2
+ RuleResult,
3
+ fact
4
+ } from "./chunk-IG5BJ2MT.js";
5
+
6
+ // src/project/project.ts
7
+ function defineGate(name, config) {
8
+ const { expects, onSatisfied, onViolation } = config;
9
+ const rule = {
10
+ id: `gate/${name}`,
11
+ description: `Feature gate: ${name} \u2014 requires: ${expects.join(", ")}`,
12
+ eventTypes: ["gate.check", `gate.${name}.check`],
13
+ contract: {
14
+ ruleId: `gate/${name}`,
15
+ behavior: `Opens gate "${name}" when all expectations are met: ${expects.join(", ")}`,
16
+ examples: [
17
+ {
18
+ given: `all expectations satisfied: ${expects.join(", ")}`,
19
+ when: "gate checked",
20
+ then: `gate.${name}.open emitted${onSatisfied ? ` \u2192 ${onSatisfied}` : ""}`
21
+ },
22
+ {
23
+ given: "one or more expectations unsatisfied",
24
+ when: "gate checked",
25
+ then: `gate.${name}.blocked emitted${onViolation ? ` \u2192 ${onViolation}` : ""}`
26
+ }
27
+ ],
28
+ invariants: [
29
+ `Gate "${name}" must never open with unsatisfied expectations`,
30
+ "Gate status must reflect current expectation state exactly"
31
+ ]
32
+ },
33
+ impl: (state, events) => {
34
+ const gateEvent = events.find(
35
+ (e) => e.tag === "gate.check" || e.tag === `gate.${name}.check`
36
+ );
37
+ if (!gateEvent) return RuleResult.skip("No gate check event");
38
+ const expectationState = state.context.expectations ?? {};
39
+ const satisfied = [];
40
+ const unsatisfied = [];
41
+ for (const exp of expects) {
42
+ if (expectationState[exp]) {
43
+ satisfied.push(exp);
44
+ } else {
45
+ unsatisfied.push(exp);
46
+ }
47
+ }
48
+ const status = unsatisfied.length === 0 ? "open" : "blocked";
49
+ const gateState = {
50
+ name,
51
+ status,
52
+ satisfied,
53
+ unsatisfied,
54
+ lastChanged: Date.now()
55
+ };
56
+ const facts = [fact(`gate.${name}.status`, gateState)];
57
+ if (status === "open" && onSatisfied) {
58
+ facts.push(fact(`gate.${name}.action`, { action: onSatisfied }));
59
+ } else if (status === "blocked" && onViolation) {
60
+ facts.push(fact(`gate.${name}.action`, { action: onViolation }));
61
+ }
62
+ return RuleResult.emit(facts);
63
+ }
64
+ };
65
+ const constraint = {
66
+ id: `gate/${name}/integrity`,
67
+ description: `Ensures gate "${name}" status matches expectation reality`,
68
+ contract: {
69
+ ruleId: `gate/${name}/integrity`,
70
+ behavior: `Validates that gate "${name}" is not open when expectations are unmet`,
71
+ examples: [
72
+ {
73
+ given: `gate ${name} is open but expectations unmet`,
74
+ when: "constraint checked",
75
+ then: "violation"
76
+ }
77
+ ],
78
+ invariants: [`Gate "${name}" must never report open when expectations are unsatisfied`]
79
+ },
80
+ impl: (state) => {
81
+ const gateState = state.context.gates?.[name];
82
+ if (!gateState) return true;
83
+ if (gateState.status === "open" && gateState.unsatisfied.length > 0) {
84
+ return `Gate "${name}" is open but has unsatisfied expectations: ${gateState.unsatisfied.join(", ")}`;
85
+ }
86
+ return true;
87
+ }
88
+ };
89
+ return { rules: [rule], constraints: [constraint] };
90
+ }
91
+ function semverContract(config) {
92
+ const { sources, invariants } = config;
93
+ const rule = {
94
+ id: "project/semver-check",
95
+ description: `Checks version consistency across: ${sources.join(", ")}`,
96
+ eventTypes: ["project.version-check"],
97
+ contract: {
98
+ ruleId: "project/semver-check",
99
+ behavior: `Verifies version consistency across ${sources.length} sources`,
100
+ examples: [
101
+ {
102
+ given: "all sources have version 1.2.3",
103
+ when: "version check runs",
104
+ then: "semver.consistent emitted"
105
+ },
106
+ {
107
+ given: "package.json has 1.2.3 but README has 1.2.2",
108
+ when: "version check runs",
109
+ then: "semver.inconsistent emitted with diff"
110
+ }
111
+ ],
112
+ invariants: invariants.length > 0 ? invariants : ["All version sources must report the same semver string"]
113
+ },
114
+ impl: (_state, events) => {
115
+ const checkEvent = events.find((e) => e.tag === "project.version-check");
116
+ if (!checkEvent) return RuleResult.skip("No version check event");
117
+ const versions = checkEvent.payload?.versions ?? {};
118
+ const versionValues = Object.values(versions);
119
+ const unique = new Set(versionValues);
120
+ if (unique.size <= 1) {
121
+ return RuleResult.emit([
122
+ fact("semver.consistent", {
123
+ version: versionValues[0] ?? "unknown",
124
+ sources: Object.keys(versions)
125
+ })
126
+ ]);
127
+ }
128
+ const report = {
129
+ consistent: false,
130
+ versions,
131
+ violations: [`Version mismatch: ${JSON.stringify(versions)}`]
132
+ };
133
+ return RuleResult.emit([fact("semver.inconsistent", report)]);
134
+ }
135
+ };
136
+ return { rules: [rule], constraints: [] };
137
+ }
138
+ function commitFromState(diff) {
139
+ const parts = [];
140
+ const bodyParts = [];
141
+ const totalAdded = diff.rulesAdded.length + diff.contractsAdded.length + diff.expectationsAdded.length;
142
+ const totalRemoved = diff.rulesRemoved.length + diff.contractsRemoved.length + diff.expectationsRemoved.length;
143
+ const totalModified = diff.rulesModified.length;
144
+ const hasGateChanges = diff.gateChanges.length > 0;
145
+ if (totalAdded > 0 && totalRemoved === 0 && totalModified === 0) {
146
+ if (diff.rulesAdded.length > 0) {
147
+ const scope = inferScope(diff.rulesAdded);
148
+ parts.push(`feat(${scope}): add ${formatIds(diff.rulesAdded)}`);
149
+ } else if (diff.contractsAdded.length > 0) {
150
+ parts.push(`feat(contracts): add contracts for ${formatIds(diff.contractsAdded)}`);
151
+ } else {
152
+ parts.push(`feat(expectations): add ${formatIds(diff.expectationsAdded)}`);
153
+ }
154
+ } else if (totalRemoved > 0 && totalAdded === 0) {
155
+ if (diff.rulesRemoved.length > 0) {
156
+ const scope = inferScope(diff.rulesRemoved);
157
+ parts.push(`refactor(${scope}): remove ${formatIds(diff.rulesRemoved)}`);
158
+ } else {
159
+ parts.push(`refactor: remove ${totalRemoved} item(s)`);
160
+ }
161
+ } else if (totalModified > 0) {
162
+ const scope = inferScope(diff.rulesModified);
163
+ parts.push(`refactor(${scope}): update ${formatIds(diff.rulesModified)}`);
164
+ } else if (hasGateChanges) {
165
+ const gateNames = diff.gateChanges.map((g) => g.gate);
166
+ parts.push(`chore(gates): ${formatIds(gateNames)} state changed`);
167
+ } else {
168
+ parts.push("chore: behavioral state update");
169
+ }
170
+ if (diff.rulesAdded.length > 0) bodyParts.push(`Rules added: ${diff.rulesAdded.join(", ")}`);
171
+ if (diff.rulesRemoved.length > 0) bodyParts.push(`Rules removed: ${diff.rulesRemoved.join(", ")}`);
172
+ if (diff.rulesModified.length > 0) bodyParts.push(`Rules modified: ${diff.rulesModified.join(", ")}`);
173
+ if (diff.contractsAdded.length > 0) bodyParts.push(`Contracts added: ${diff.contractsAdded.join(", ")}`);
174
+ if (diff.contractsRemoved.length > 0) bodyParts.push(`Contracts removed: ${diff.contractsRemoved.join(", ")}`);
175
+ if (diff.expectationsAdded.length > 0) bodyParts.push(`Expectations added: ${diff.expectationsAdded.join(", ")}`);
176
+ if (diff.expectationsRemoved.length > 0) bodyParts.push(`Expectations removed: ${diff.expectationsRemoved.join(", ")}`);
177
+ for (const gc of diff.gateChanges) {
178
+ bodyParts.push(`Gate "${gc.gate}": ${gc.from} \u2192 ${gc.to}`);
179
+ }
180
+ const subject = parts[0] || "chore: update";
181
+ return bodyParts.length > 0 ? `${subject}
182
+
183
+ ${bodyParts.join("\n")}` : subject;
184
+ }
185
+ function inferScope(ids) {
186
+ if (ids.length === 0) return "rules";
187
+ const prefixes = ids.map((id) => {
188
+ const slash = id.indexOf("/");
189
+ return slash > 0 ? id.slice(0, slash) : id;
190
+ });
191
+ const unique = new Set(prefixes);
192
+ return unique.size === 1 ? prefixes[0] : "rules";
193
+ }
194
+ function formatIds(ids) {
195
+ if (ids.length <= 3) return ids.join(", ");
196
+ return `${ids.slice(0, 2).join(", ")} (+${ids.length - 2} more)`;
197
+ }
198
+ function branchRules(config) {
199
+ const { naming, mergeConditions } = config;
200
+ const namePattern = naming.replace("{name}", "(.+)").replace("{issue}", "(\\d+)");
201
+ const nameRegex = new RegExp(`^${namePattern}$`);
202
+ const rule = {
203
+ id: "project/branch-check",
204
+ description: `Validates branch naming (${naming}) and merge conditions`,
205
+ eventTypes: ["project.branch-check"],
206
+ contract: {
207
+ ruleId: "project/branch-check",
208
+ behavior: `Ensures branch follows "${naming}" pattern and merge conditions are met`,
209
+ examples: [
210
+ {
211
+ given: `branch named "${naming.replace("{name}", "my-feature")}"`,
212
+ when: "branch checked",
213
+ then: "branch.valid emitted"
214
+ },
215
+ {
216
+ given: 'branch named "random-name"',
217
+ when: "branch checked",
218
+ then: "branch.invalid emitted"
219
+ }
220
+ ],
221
+ invariants: [
222
+ `Branch names must follow pattern: ${naming}`,
223
+ `Merge requires: ${mergeConditions.join(", ")}`
224
+ ]
225
+ },
226
+ impl: (_state, events) => {
227
+ const checkEvent = events.find((e) => e.tag === "project.branch-check");
228
+ if (!checkEvent) return RuleResult.skip("No branch check event");
229
+ const payload = checkEvent.payload;
230
+ const branch = payload.branch ?? "";
231
+ const conditions = payload.conditions ?? {};
232
+ const validName = nameRegex.test(branch);
233
+ const unmetConditions = mergeConditions.filter((c) => !conditions[c]);
234
+ if (validName && unmetConditions.length === 0) {
235
+ return RuleResult.emit([
236
+ fact("branch.valid", { branch, mergeReady: true })
237
+ ]);
238
+ }
239
+ const reasons = [];
240
+ if (!validName) reasons.push(`Branch name "${branch}" doesn't match pattern "${naming}"`);
241
+ if (unmetConditions.length > 0) reasons.push(`Unmet merge conditions: ${unmetConditions.join(", ")}`);
242
+ return RuleResult.emit([
243
+ fact("branch.invalid", { branch, reasons, mergeReady: false })
244
+ ]);
245
+ }
246
+ };
247
+ return { rules: [rule], constraints: [] };
248
+ }
249
+ function lintGate(config = {}) {
250
+ const expects = ["lint-passes", ...config.additionalExpects ?? []];
251
+ return defineGate("lint", {
252
+ expects,
253
+ onSatisfied: "lint-passed",
254
+ onViolation: "lint-failed"
255
+ });
256
+ }
257
+ function formatGate(config = {}) {
258
+ const expects = ["format-passes", ...config.additionalExpects ?? []];
259
+ return defineGate("format", {
260
+ expects,
261
+ onSatisfied: "format-passed",
262
+ onViolation: "format-failed"
263
+ });
264
+ }
265
+ function expectationGate(config = {}) {
266
+ const expects = ["expectations-verified", ...config.additionalExpects ?? []];
267
+ return defineGate("expectations", {
268
+ expects,
269
+ onSatisfied: "expectations-passed",
270
+ onViolation: "expectations-failed"
271
+ });
272
+ }
273
+
274
+ export {
275
+ defineGate,
276
+ semverContract,
277
+ commitFromState,
278
+ branchRules,
279
+ lintGate,
280
+ formatGate,
281
+ expectationGate
282
+ };
@@ -0,0 +1,91 @@
1
+ // src/core/rule-result.ts
2
+ var RuleResult = class _RuleResult {
3
+ /** The kind of result */
4
+ kind;
5
+ /** Facts produced (only for 'emit') */
6
+ facts;
7
+ /** Fact tags to retract (only for 'retract') */
8
+ retractTags;
9
+ /** Optional reason (for noop/skip/retract — useful for debugging) */
10
+ reason;
11
+ /** The rule ID that produced this result (set by engine) */
12
+ ruleId;
13
+ constructor(kind, facts, retractTags, reason) {
14
+ this.kind = kind;
15
+ this.facts = facts;
16
+ this.retractTags = retractTags;
17
+ this.reason = reason;
18
+ }
19
+ /**
20
+ * Rule produced facts.
21
+ *
22
+ * @example
23
+ * return RuleResult.emit([
24
+ * { tag: 'sprint.behind', payload: { deficit: 5 } }
25
+ * ]);
26
+ */
27
+ static emit(facts) {
28
+ if (facts.length === 0) {
29
+ throw new Error(
30
+ "RuleResult.emit() requires at least one fact. Use RuleResult.noop() or RuleResult.skip() when a rule has nothing to say."
31
+ );
32
+ }
33
+ return new _RuleResult("emit", facts, []);
34
+ }
35
+ /**
36
+ * Rule evaluated but had nothing to report.
37
+ * Unlike returning [], this is explicit and traceable.
38
+ *
39
+ * @example
40
+ * if (ctx.completedHours >= expectedHours) {
41
+ * return RuleResult.noop('Sprint is on pace');
42
+ * }
43
+ */
44
+ static noop(reason) {
45
+ return new _RuleResult("noop", [], [], reason);
46
+ }
47
+ /**
48
+ * Rule decided to skip because preconditions were not met.
49
+ * Distinct from noop: skip means "I can't evaluate", noop means "I evaluated and found nothing".
50
+ *
51
+ * @example
52
+ * if (!ctx.sprintName) {
53
+ * return RuleResult.skip('No active sprint');
54
+ * }
55
+ */
56
+ static skip(reason) {
57
+ return new _RuleResult("skip", [], [], reason);
58
+ }
59
+ /**
60
+ * Rule retracts previously emitted facts by tag.
61
+ * Used when a condition that previously produced facts is no longer true.
62
+ *
63
+ * @example
64
+ * // Sprint was behind, but caught up
65
+ * if (ctx.completedHours >= expectedHours) {
66
+ * return RuleResult.retract(['sprint.behind'], 'Sprint caught up');
67
+ * }
68
+ */
69
+ static retract(tags, reason) {
70
+ if (tags.length === 0) {
71
+ throw new Error("RuleResult.retract() requires at least one tag.");
72
+ }
73
+ return new _RuleResult("retract", [], tags, reason);
74
+ }
75
+ /** Whether this result produced facts */
76
+ get hasFacts() {
77
+ return this.facts.length > 0;
78
+ }
79
+ /** Whether this result retracts facts */
80
+ get hasRetractions() {
81
+ return this.retractTags.length > 0;
82
+ }
83
+ };
84
+ function fact(tag, payload) {
85
+ return { tag, payload };
86
+ }
87
+
88
+ export {
89
+ RuleResult,
90
+ fact
91
+ };
@@ -1,90 +1,10 @@
1
+ import {
2
+ RuleResult
3
+ } from "./chunk-IG5BJ2MT.js";
4
+
1
5
  // src/core/protocol.ts
2
6
  var PRAXIS_PROTOCOL_VERSION = "1.0.0";
3
7
 
4
- // src/core/rule-result.ts
5
- var RuleResult = class _RuleResult {
6
- /** The kind of result */
7
- kind;
8
- /** Facts produced (only for 'emit') */
9
- facts;
10
- /** Fact tags to retract (only for 'retract') */
11
- retractTags;
12
- /** Optional reason (for noop/skip/retract — useful for debugging) */
13
- reason;
14
- /** The rule ID that produced this result (set by engine) */
15
- ruleId;
16
- constructor(kind, facts, retractTags, reason) {
17
- this.kind = kind;
18
- this.facts = facts;
19
- this.retractTags = retractTags;
20
- this.reason = reason;
21
- }
22
- /**
23
- * Rule produced facts.
24
- *
25
- * @example
26
- * return RuleResult.emit([
27
- * { tag: 'sprint.behind', payload: { deficit: 5 } }
28
- * ]);
29
- */
30
- static emit(facts) {
31
- if (facts.length === 0) {
32
- throw new Error(
33
- "RuleResult.emit() requires at least one fact. Use RuleResult.noop() or RuleResult.skip() when a rule has nothing to say."
34
- );
35
- }
36
- return new _RuleResult("emit", facts, []);
37
- }
38
- /**
39
- * Rule evaluated but had nothing to report.
40
- * Unlike returning [], this is explicit and traceable.
41
- *
42
- * @example
43
- * if (ctx.completedHours >= expectedHours) {
44
- * return RuleResult.noop('Sprint is on pace');
45
- * }
46
- */
47
- static noop(reason) {
48
- return new _RuleResult("noop", [], [], reason);
49
- }
50
- /**
51
- * Rule decided to skip because preconditions were not met.
52
- * Distinct from noop: skip means "I can't evaluate", noop means "I evaluated and found nothing".
53
- *
54
- * @example
55
- * if (!ctx.sprintName) {
56
- * return RuleResult.skip('No active sprint');
57
- * }
58
- */
59
- static skip(reason) {
60
- return new _RuleResult("skip", [], [], reason);
61
- }
62
- /**
63
- * Rule retracts previously emitted facts by tag.
64
- * Used when a condition that previously produced facts is no longer true.
65
- *
66
- * @example
67
- * // Sprint was behind, but caught up
68
- * if (ctx.completedHours >= expectedHours) {
69
- * return RuleResult.retract(['sprint.behind'], 'Sprint caught up');
70
- * }
71
- */
72
- static retract(tags, reason) {
73
- if (tags.length === 0) {
74
- throw new Error("RuleResult.retract() requires at least one tag.");
75
- }
76
- return new _RuleResult("retract", [], tags, reason);
77
- }
78
- /** Whether this result produced facts */
79
- get hasFacts() {
80
- return this.facts.length > 0;
81
- }
82
- /** Whether this result retracts facts */
83
- get hasRetractions() {
84
- return this.retractTags.length > 0;
85
- }
86
- };
87
-
88
8
  // src/core/engine.ts
89
9
  function safeClone(value) {
90
10
  if (value === null || typeof value !== "object") {