musubix3 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/.github/plugin/marketplace.json +16 -0
  2. package/.github/skills/sdd-change/SKILL.md +78 -0
  3. package/.github/skills/sdd-design/SKILL.md +26 -0
  4. package/.github/skills/sdd-formal-codegraph/SKILL.md +53 -0
  5. package/.github/skills/sdd-implementation/SKILL.md +54 -0
  6. package/.github/skills/sdd-knowledge/SKILL.md +24 -0
  7. package/.github/skills/sdd-quality/SKILL.md +69 -0
  8. package/.github/skills/sdd-requirements/SKILL.md +42 -0
  9. package/.github/skills/sdd-traceability/SKILL.md +28 -0
  10. package/CHANGELOG.md +152 -0
  11. package/LICENSE +21 -0
  12. package/README-ja.md +573 -0
  13. package/README.md +639 -0
  14. package/assets/ADR-0001.md +15 -0
  15. package/assets/constitution.md +17 -0
  16. package/assets/design.md +13 -0
  17. package/assets/requirements.md +15 -0
  18. package/dist/packages/analysis/src/adapters.d.ts +12 -0
  19. package/dist/packages/analysis/src/adapters.js +167 -0
  20. package/dist/packages/analysis/src/adapters.js.map +1 -0
  21. package/dist/packages/analysis/src/attestation.d.ts +50 -0
  22. package/dist/packages/analysis/src/attestation.js +474 -0
  23. package/dist/packages/analysis/src/attestation.js.map +1 -0
  24. package/dist/packages/analysis/src/change.d.ts +53 -0
  25. package/dist/packages/analysis/src/change.js +337 -0
  26. package/dist/packages/analysis/src/change.js.map +1 -0
  27. package/dist/packages/analysis/src/config.d.ts +104 -0
  28. package/dist/packages/analysis/src/config.js +456 -0
  29. package/dist/packages/analysis/src/config.js.map +1 -0
  30. package/dist/packages/analysis/src/files.d.ts +13 -0
  31. package/dist/packages/analysis/src/files.js +103 -0
  32. package/dist/packages/analysis/src/files.js.map +1 -0
  33. package/dist/packages/analysis/src/formal.d.ts +82 -0
  34. package/dist/packages/analysis/src/formal.js +579 -0
  35. package/dist/packages/analysis/src/formal.js.map +1 -0
  36. package/dist/packages/analysis/src/gate.d.ts +56 -0
  37. package/dist/packages/analysis/src/gate.js +484 -0
  38. package/dist/packages/analysis/src/gate.js.map +1 -0
  39. package/dist/packages/analysis/src/graph.d.ts +52 -0
  40. package/dist/packages/analysis/src/graph.js +737 -0
  41. package/dist/packages/analysis/src/graph.js.map +1 -0
  42. package/dist/packages/analysis/src/index.d.ts +17 -0
  43. package/dist/packages/analysis/src/index.js +18 -0
  44. package/dist/packages/analysis/src/index.js.map +1 -0
  45. package/dist/packages/analysis/src/knowledge.d.ts +29 -0
  46. package/dist/packages/analysis/src/knowledge.js +95 -0
  47. package/dist/packages/analysis/src/knowledge.js.map +1 -0
  48. package/dist/packages/analysis/src/model-correspondence.d.ts +45 -0
  49. package/dist/packages/analysis/src/model-correspondence.js +310 -0
  50. package/dist/packages/analysis/src/model-correspondence.js.map +1 -0
  51. package/dist/packages/analysis/src/mutation.d.ts +54 -0
  52. package/dist/packages/analysis/src/mutation.js +299 -0
  53. package/dist/packages/analysis/src/mutation.js.map +1 -0
  54. package/dist/packages/analysis/src/order.d.ts +23 -0
  55. package/dist/packages/analysis/src/order.js +89 -0
  56. package/dist/packages/analysis/src/order.js.map +1 -0
  57. package/dist/packages/analysis/src/performance.d.ts +65 -0
  58. package/dist/packages/analysis/src/performance.js +319 -0
  59. package/dist/packages/analysis/src/performance.js.map +1 -0
  60. package/dist/packages/analysis/src/process.d.ts +14 -0
  61. package/dist/packages/analysis/src/process.js +79 -0
  62. package/dist/packages/analysis/src/process.js.map +1 -0
  63. package/dist/packages/analysis/src/tdd.d.ts +65 -0
  64. package/dist/packages/analysis/src/tdd.js +353 -0
  65. package/dist/packages/analysis/src/tdd.js.map +1 -0
  66. package/dist/packages/analysis/src/trace.d.ts +45 -0
  67. package/dist/packages/analysis/src/trace.js +242 -0
  68. package/dist/packages/analysis/src/trace.js.map +1 -0
  69. package/dist/packages/analysis/src/workflow.d.ts +59 -0
  70. package/dist/packages/analysis/src/workflow.js +405 -0
  71. package/dist/packages/analysis/src/workflow.js.map +1 -0
  72. package/dist/packages/cli/src/install.d.ts +15 -0
  73. package/dist/packages/cli/src/install.js +71 -0
  74. package/dist/packages/cli/src/install.js.map +1 -0
  75. package/dist/packages/cli/src/main.d.ts +3 -0
  76. package/dist/packages/cli/src/main.js +364 -0
  77. package/dist/packages/cli/src/main.js.map +1 -0
  78. package/dist/packages/domain/src/constitution.d.ts +3 -0
  79. package/dist/packages/domain/src/constitution.js +46 -0
  80. package/dist/packages/domain/src/constitution.js.map +1 -0
  81. package/dist/packages/domain/src/design.d.ts +8 -0
  82. package/dist/packages/domain/src/design.js +59 -0
  83. package/dist/packages/domain/src/design.js.map +1 -0
  84. package/dist/packages/domain/src/index.d.ts +5 -0
  85. package/dist/packages/domain/src/index.js +6 -0
  86. package/dist/packages/domain/src/index.js.map +1 -0
  87. package/dist/packages/domain/src/markdown.d.ts +16 -0
  88. package/dist/packages/domain/src/markdown.js +72 -0
  89. package/dist/packages/domain/src/markdown.js.map +1 -0
  90. package/dist/packages/domain/src/requirements.d.ts +3 -0
  91. package/dist/packages/domain/src/requirements.js +195 -0
  92. package/dist/packages/domain/src/requirements.js.map +1 -0
  93. package/dist/packages/domain/src/types.d.ts +100 -0
  94. package/dist/packages/domain/src/types.js +14 -0
  95. package/dist/packages/domain/src/types.js.map +1 -0
  96. package/package.json +59 -0
  97. package/plugin.json +9 -0
@@ -0,0 +1,82 @@
1
+ import { type Diagnostic, type FormalConstraint } from '../../domain/src/index.js';
2
+ import { type Runner } from './process.js';
3
+ export type Solver = 'auto' | 'none' | 'z3' | 'lean';
4
+ export type FormalFormat = 'smt2' | 'lean';
5
+ export type SolverStatus = 'not-requested' | 'missing' | 'sat' | 'unsat' | 'checked' | 'unknown' | 'timeout' | 'error';
6
+ export interface FormalLiteral {
7
+ requirement: string;
8
+ atom: string;
9
+ positive: boolean;
10
+ }
11
+ export interface FormalConstraintEntry {
12
+ requirement: string;
13
+ constraint: FormalConstraint;
14
+ }
15
+ export interface FormalArtifact {
16
+ format: FormalFormat;
17
+ path: string;
18
+ sha256: string;
19
+ description: string;
20
+ }
21
+ export interface SolverReport {
22
+ requested: Solver;
23
+ name: string;
24
+ command: string;
25
+ version: string;
26
+ status: SolverStatus;
27
+ durationMs: number;
28
+ output: string;
29
+ artifact?: FormalArtifact;
30
+ }
31
+ export interface FormalResult {
32
+ valid: boolean;
33
+ abstraction: string;
34
+ assumptions: string[];
35
+ literals: FormalLiteral[];
36
+ constraints: FormalConstraintEntry[];
37
+ unsupported: string[];
38
+ diagnostics: Diagnostic[];
39
+ consistency: 'consistent' | 'inconsistent' | 'unknown';
40
+ solver: SolverReport;
41
+ }
42
+ export interface FormalCheckOptions {
43
+ solver?: Solver;
44
+ timeoutMs?: number;
45
+ z3Command?: string;
46
+ leanCommand?: string;
47
+ }
48
+ export interface FormalGenerationResult {
49
+ valid: boolean;
50
+ consistency: FormalResult['consistency'];
51
+ literals: FormalLiteral[];
52
+ constraints: FormalConstraintEntry[];
53
+ unsupported: string[];
54
+ diagnostics: Diagnostic[];
55
+ artifacts: FormalArtifact[];
56
+ }
57
+ export interface SolverDoctorEntry {
58
+ name: 'z3' | 'lean';
59
+ available: boolean;
60
+ command: string;
61
+ version: string;
62
+ status: 'available' | 'missing' | 'timeout' | 'error';
63
+ output: string;
64
+ }
65
+ export interface SolverDoctorResult {
66
+ available: boolean;
67
+ solvers: SolverDoctorEntry[];
68
+ }
69
+ interface FormalModel {
70
+ validRequirements: boolean;
71
+ literals: FormalLiteral[];
72
+ constraints: FormalConstraintEntry[];
73
+ unsupported: string[];
74
+ diagnostics: Diagnostic[];
75
+ consistency: FormalResult['consistency'];
76
+ }
77
+ export declare function generateSmt2(model: Pick<FormalModel, 'literals'> & Partial<Pick<FormalModel, 'constraints'>>): string;
78
+ export declare function generateLean(model: Pick<FormalModel, 'literals' | 'consistency'> & Partial<Pick<FormalModel, 'constraints'>>): string;
79
+ export declare function generateFormalArtifacts(text: string, root: string, formats?: FormalFormat[]): Promise<FormalGenerationResult>;
80
+ export declare function formalDoctor(root: string, options?: FormalCheckOptions, runner?: Runner): Promise<SolverDoctorResult>;
81
+ export declare function formalCheck(text: string, root: string, solverOrOptions?: Solver | FormalCheckOptions, runner?: Runner): Promise<FormalResult>;
82
+ export {};
@@ -0,0 +1,579 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { error, validateRequirements } from '../../domain/src/index.js';
3
+ import { runProcess } from './process.js';
4
+ import { writeText } from './files.js';
5
+ const abstraction = 'Consistency of explicit branch-scoped conditional, exact unit-normalized integer numeric, bounded temporal interval, deterministic state-transition, and controlled unconditional Boolean constraints; not implementation correctness.';
6
+ const assumptions = [
7
+ 'Exact normalized subject/response pairs denote the same Boolean proposition.',
8
+ 'Explicit Formal JSON fields use only their documented closed-world constraint semantics.',
9
+ 'Temporal constraints model one nonnegative integer response delay in milliseconds within the intersection of declared afterMs/withinMs intervals, not scheduling or liveness.',
10
+ 'Numeric ms/s/min values share an exact millisecond dimension; bytes/kib/mib values share an exact byte dimension; other and incompatible units remain separate.',
11
+ 'State transitions require one target for each declared from-state/event pair; undeclared transitions are unconstrained.',
12
+ 'Copilot supplies candidate requirements; deterministic checks do not establish their truth.',
13
+ ];
14
+ function structuredKey(...parts) {
15
+ return JSON.stringify(parts);
16
+ }
17
+ function conditionalKey(constraint) {
18
+ return structuredKey('conditional', constraint.condition, constraint.conditionValue, constraint.consequence);
19
+ }
20
+ function temporalKey(constraint) {
21
+ return structuredKey('delay', constraint.trigger, constraint.response);
22
+ }
23
+ function transitionKey(constraint) {
24
+ return structuredKey('transition', constraint.from, constraint.event);
25
+ }
26
+ const unitConversions = new Map([
27
+ ['ms', { dimension: 'duration', canonical: 'ms', factor: 1n }],
28
+ ['s', { dimension: 'duration', canonical: 'ms', factor: 1000n }],
29
+ ['min', { dimension: 'duration', canonical: 'ms', factor: 60000n }],
30
+ ['bytes', { dimension: 'size', canonical: 'bytes', factor: 1n }],
31
+ ['kib', { dimension: 'size', canonical: 'bytes', factor: 1024n }],
32
+ ['mib', { dimension: 'size', canonical: 'bytes', factor: 1048576n }],
33
+ ]);
34
+ function normalizeNumeric(constraint) {
35
+ const normalizedUnit = constraint.unit?.normalize('NFKC').toLowerCase();
36
+ const conversion = normalizedUnit ? unitConversions.get(normalizedUnit) : undefined;
37
+ const dimension = conversion
38
+ ? `${conversion.dimension}(${conversion.canonical})`
39
+ : normalizedUnit ? `unit(${normalizedUnit})` : 'unitless';
40
+ return {
41
+ key: structuredKey('metric', constraint.metric, dimension),
42
+ value: BigInt(constraint.value) * (conversion?.factor ?? 1n),
43
+ };
44
+ }
45
+ function satisfies(candidate, operator, expected) {
46
+ return operator === '<' ? candidate < expected
47
+ : operator === '<=' ? candidate <= expected
48
+ : operator === '=' ? candidate === expected
49
+ : operator === '>=' ? candidate >= expected
50
+ : candidate > expected;
51
+ }
52
+ function constraintDiagnostics(constraints) {
53
+ const diagnostics = [];
54
+ const numeric = new Map();
55
+ const temporal = new Map();
56
+ const transitions = new Map();
57
+ const conditionals = new Map();
58
+ for (const entry of constraints) {
59
+ const constraint = entry.constraint;
60
+ if (constraint.kind === 'numeric') {
61
+ const normalized = normalizeNumeric(constraint);
62
+ numeric.set(normalized.key, [...numeric.get(normalized.key) ?? [], {
63
+ requirement: entry.requirement,
64
+ operator: constraint.operator,
65
+ value: normalized.value,
66
+ }]);
67
+ }
68
+ else if (constraint.kind === 'temporal') {
69
+ const key = temporalKey(constraint);
70
+ temporal.set(key, [...temporal.get(key) ?? [], entry]);
71
+ }
72
+ else if (constraint.kind === 'transition') {
73
+ const key = transitionKey(constraint);
74
+ transitions.set(key, [...transitions.get(key) ?? [], entry]);
75
+ }
76
+ else {
77
+ const key = conditionalKey(constraint);
78
+ conditionals.set(key, [...conditionals.get(key) ?? [], entry]);
79
+ }
80
+ }
81
+ for (const entries of numeric.values()) {
82
+ const candidates = new Set();
83
+ for (const entry of entries) {
84
+ candidates.add(entry.value);
85
+ candidates.add(entry.value - 1n);
86
+ candidates.add(entry.value + 1n);
87
+ }
88
+ if (![...candidates].some((candidate) => entries.every((entry) => satisfies(candidate, entry.operator, entry.value)))) {
89
+ diagnostics.push(error('FORMAL_NUMERIC_CONTRADICTION', `${entries.map((entry) => entry.requirement).join(', ')} impose incompatible numeric bounds.`));
90
+ }
91
+ }
92
+ for (const entries of temporal.values()) {
93
+ const intervals = entries.map((entry) => entry.constraint);
94
+ const lower = intervals.reduce((maximum, interval) => Math.max(maximum, interval.afterMs ?? 0), 0);
95
+ const upper = intervals.reduce((minimum, interval) => Math.min(minimum, interval.withinMs), Number.MAX_SAFE_INTEGER);
96
+ if (lower > upper) {
97
+ diagnostics.push(error('FORMAL_TEMPORAL_CONTRADICTION', `${entries.map((entry) => entry.requirement).join(', ')} impose incompatible response-delay intervals.`));
98
+ }
99
+ }
100
+ for (const entries of transitions.values()) {
101
+ const targets = new Set(entries.map((entry) => entry.constraint.to));
102
+ if (targets.size > 1) {
103
+ diagnostics.push(error('FORMAL_TRANSITION_CONTRADICTION', `${entries.map((entry) => entry.requirement).join(', ')} require different targets for the same transition.`));
104
+ }
105
+ }
106
+ for (const entries of conditionals.values()) {
107
+ const values = new Set(entries.map((entry) => entry.constraint.consequenceValue));
108
+ if (values.size > 1) {
109
+ diagnostics.push(error('FORMAL_CONDITIONAL_CONTRADICTION', `${entries.map((entry) => entry.requirement).join(', ')} require opposing consequences under the same condition branch.`));
110
+ }
111
+ }
112
+ return diagnostics;
113
+ }
114
+ function literal(requirement) {
115
+ // Trigger overlap and domain equivalence must not be guessed.
116
+ if (requirement.pattern !== 'ubiquitous')
117
+ return null;
118
+ const english = /^(?:the|a|an)\s+(.+?)\s+shall\s+(not\s+)?(.+?)[.!]?$/i.exec(requirement.statement);
119
+ const japanese = /^(.+?)は(.+?)(してはならない|してはいけない|しなければならない)[。.]?$/.exec(requirement.statement);
120
+ if (!english && !japanese)
121
+ return null;
122
+ const subject = (english?.[1] ?? japanese?.[1] ?? '').trim();
123
+ const response = (english?.[3] ?? japanese?.[2] ?? '').replace(/[。.]$/, '').trim();
124
+ if (/\b(and|or|unless|except|if|when|while|within|at least|at most)\b|[<>=\d]/i.test(response))
125
+ return null;
126
+ if (/(?:かつ|又は|若しくは|場合|とき|間|以内|以上|以下)|\d/.test(response))
127
+ return null;
128
+ const atom = structuredKey(subject.normalize('NFKC').toLowerCase().replace(/\s+/g, ' ').trim(), response.normalize('NFKC').toLowerCase().replace(/\s+/g, ' ').trim());
129
+ const japaneseNegative = japanese?.[3] === 'してはならない' || japanese?.[3] === 'してはいけない';
130
+ return { requirement: requirement.id, atom, positive: english ? !english[2] : !japaneseNegative };
131
+ }
132
+ function buildModel(text) {
133
+ const requirements = validateRequirements(text);
134
+ const literals = [];
135
+ const constraints = [];
136
+ const unsupported = [];
137
+ for (const requirement of requirements.value) {
138
+ if (requirement.formal) {
139
+ constraints.push({ requirement: requirement.id, constraint: requirement.formal });
140
+ continue;
141
+ }
142
+ const parsed = literal(requirement);
143
+ if (parsed)
144
+ literals.push(parsed);
145
+ else
146
+ unsupported.push(requirement.id);
147
+ }
148
+ const diagnostics = [...requirements.diagnostics];
149
+ for (const id of unsupported) {
150
+ diagnostics.push({
151
+ code: 'FORMAL_UNSUPPORTED',
152
+ severity: 'warning',
153
+ message: `${id} is outside the unconditional English/Japanese Boolean abstraction; it was not checked for consistency.`,
154
+ });
155
+ }
156
+ for (let i = 0; i < literals.length; i++) {
157
+ for (let j = i + 1; j < literals.length; j++) {
158
+ const first = literals[i];
159
+ const second = literals[j];
160
+ if (first.atom === second.atom && first.positive !== second.positive) {
161
+ diagnostics.push(error('FORMAL_CONTRADICTION', `${first.requirement} contradicts ${second.requirement} in the propositional abstraction.`));
162
+ }
163
+ }
164
+ }
165
+ diagnostics.push(...constraintDiagnostics(constraints));
166
+ const inconsistent = diagnostics.some((diagnostic) => diagnostic.code.includes('CONTRADICTION'));
167
+ return {
168
+ validRequirements: requirements.valid,
169
+ literals,
170
+ constraints,
171
+ unsupported,
172
+ diagnostics,
173
+ consistency: inconsistent ? 'inconsistent' : (literals.length || constraints.length) && requirements.valid ? 'consistent' : 'unknown',
174
+ };
175
+ }
176
+ function atomsOf(model) {
177
+ return [...new Set(model.literals.map((entry) => entry.atom))].sort();
178
+ }
179
+ function assertionName(requirement, index) {
180
+ return `${requirement.toLowerCase().replace(/[^a-z0-9_]/g, '_')}_${index}`;
181
+ }
182
+ export function generateSmt2(model) {
183
+ const constraints = model.constraints ?? [];
184
+ const atoms = atomsOf({ ...model, constraints, validRequirements: true, unsupported: [], diagnostics: [], consistency: 'unknown' });
185
+ const atomName = (entry) => `p${atoms.indexOf(entry.atom)}`;
186
+ const conditionalScenarios = [...new Set(constraints.flatMap((entry) => entry.constraint.kind === 'conditional'
187
+ ? [conditionalKey(entry.constraint)] : []))].sort();
188
+ const integerVariables = [...new Set(constraints.flatMap((entry) => entry.constraint.kind === 'numeric'
189
+ ? [normalizeNumeric(entry.constraint).key]
190
+ : entry.constraint.kind === 'temporal' ? [temporalKey(entry.constraint)]
191
+ : entry.constraint.kind === 'transition' ? [transitionKey(entry.constraint)] : []))].sort();
192
+ const states = [...new Set(constraints.flatMap((entry) => entry.constraint.kind === 'transition' ? [entry.constraint.to] : []))].sort();
193
+ const integerName = (key) => `n${integerVariables.indexOf(key)}`;
194
+ const explicit = constraints.flatMap((entry, index) => {
195
+ const constraint = entry.constraint;
196
+ const named = (suffix, value) => `(assert (! ${value} :named ${assertionName(entry.requirement, index)}_${suffix}))`;
197
+ if (constraint.kind === 'conditional') {
198
+ const scenario = conditionalKey(constraint);
199
+ const consequence = `q${conditionalScenarios.indexOf(scenario)}`;
200
+ return [named('conditional', constraint.consequenceValue ? consequence : `(not ${consequence})`)];
201
+ }
202
+ if (constraint.kind === 'numeric') {
203
+ const normalized = normalizeNumeric(constraint);
204
+ return [named('numeric', `(${constraint.operator} ${integerName(normalized.key)} ${normalized.value})`)];
205
+ }
206
+ if (constraint.kind === 'temporal') {
207
+ const variable = integerName(temporalKey(constraint));
208
+ return [
209
+ named('after', `(>= ${variable} ${constraint.afterMs ?? 0})`),
210
+ named('deadline', `(<= ${variable} ${constraint.withinMs})`),
211
+ ];
212
+ }
213
+ return [named('transition', `(= ${integerName(transitionKey(constraint))} ${states.indexOf(constraint.to)})`)];
214
+ });
215
+ return [
216
+ '; Generated by musubix3. This checks only the documented explicit constraint model.',
217
+ '(set-logic QF_UFLIA)',
218
+ '(set-option :produce-unsat-cores true)',
219
+ ...atoms.map((atom, index) => `; p${index}: ${atom}\n(declare-fun p${index} () Bool)`),
220
+ ...conditionalScenarios.map((scenario, index) => `; q${index}: consequence under ${scenario}\n(declare-fun q${index} () Bool)`),
221
+ ...integerVariables.map((name, index) => `; n${index}: ${name}\n(declare-fun n${index} () Int)`),
222
+ ...states.map((state, index) => `; transition-state ${index}: ${state}`),
223
+ ...model.literals.map((entry, index) => `(assert (! ${entry.positive ? atomName(entry) : `(not ${atomName(entry)})`} :named ${assertionName(entry.requirement, index)}))`),
224
+ ...explicit,
225
+ '(check-sat)',
226
+ ].join('\n');
227
+ }
228
+ export function generateLean(model) {
229
+ const constraints = model.constraints ?? [];
230
+ const atoms = atomsOf({ ...model, constraints, validRequirements: true, unsupported: [], diagnostics: [] });
231
+ const parameters = atoms.map((_, index) => `(p${index} : Bool)`).join(' ');
232
+ const atomName = (entry) => `p${atoms.indexOf(entry.atom)}`;
233
+ const expression = model.literals.map((entry) => entry.positive ? atomName(entry) : `!${atomName(entry)}`).join(' && ') || 'true';
234
+ const quantified = atoms.length
235
+ ? `∃ ${atoms.map((_, index) => `p${index}`).join(' ')} : Bool, obligations ${atoms.map((_, index) => `p${index}`).join(' ')} = true`
236
+ : 'obligations = true';
237
+ const booleanContradiction = model.literals.some((entry, index) => model.literals.slice(index + 1).some((other) => other.atom === entry.atom && other.positive !== entry.positive));
238
+ const explicitInconsistent = booleanContradiction || constraintDiagnostics(constraints)
239
+ .some((diagnostic) => diagnostic.code.includes('CONTRADICTION'));
240
+ const proposition = booleanContradiction ? `¬ (${quantified})` : quantified;
241
+ const literalWitnesses = atoms.map((atom) => model.literals.find((entry) => entry.atom === atom)?.positive !== false);
242
+ const lines = [
243
+ ...(constraints.length ? ['import Lean.Elab.Tactic.Omega', ''] : []),
244
+ '/- Generated by musubix3.',
245
+ ' This checks the normalized machine-readable abstraction,',
246
+ ' not correctness of the requirements or implementation. -/',
247
+ `def obligations ${parameters} : Bool := ${expression}`,
248
+ '',
249
+ `theorem requirements_abstraction : ${proposition} := by`,
250
+ ...(atoms.length === 0
251
+ ? [' rfl']
252
+ : booleanContradiction
253
+ ? [' simp [obligations]']
254
+ : [` refine ⟨${literalWitnesses.map(String).join(', ')}, ?_⟩`, ' simp [obligations]']),
255
+ ];
256
+ if (constraints.length) {
257
+ const conditionalScenarios = [...new Set(constraints.flatMap((entry) => entry.constraint.kind === 'conditional'
258
+ ? [conditionalKey(entry.constraint)] : []))].sort();
259
+ const integerVariables = [...new Set(constraints.flatMap((entry) => entry.constraint.kind === 'numeric'
260
+ ? [normalizeNumeric(entry.constraint).key]
261
+ : entry.constraint.kind === 'temporal' ? [temporalKey(entry.constraint)]
262
+ : entry.constraint.kind === 'transition' ? [transitionKey(entry.constraint)] : []))].sort();
263
+ const states = [...new Set(constraints.flatMap((entry) => entry.constraint.kind === 'transition' ? [entry.constraint.to] : []))].sort();
264
+ const booleanParameters = [
265
+ ...atoms.map((_, index) => `p${index}`),
266
+ ...conditionalScenarios.map((_, index) => `q${index}`),
267
+ ];
268
+ const integerParameters = integerVariables.map((_, index) => `n${index}`);
269
+ const declarations = [
270
+ ...booleanParameters.map((name) => `(${name} : Bool)`),
271
+ ...integerParameters.map((name) => `(${name} : Int)`),
272
+ ].join(' ');
273
+ const propositions = [
274
+ ...model.literals.map((entry) => `${atomName(entry)} = ${entry.positive ? 'true' : 'false'}`),
275
+ ...constraints.flatMap((entry) => {
276
+ const constraint = entry.constraint;
277
+ if (constraint.kind === 'conditional') {
278
+ const scenario = conditionalKey(constraint);
279
+ return [`q${conditionalScenarios.indexOf(scenario)} = ${constraint.consequenceValue ? 'true' : 'false'}`];
280
+ }
281
+ if (constraint.kind === 'numeric') {
282
+ const normalized = normalizeNumeric(constraint);
283
+ return [`n${integerVariables.indexOf(normalized.key)} ${constraint.operator} ${normalized.value}`];
284
+ }
285
+ if (constraint.kind === 'temporal') {
286
+ const variable = `n${integerVariables.indexOf(temporalKey(constraint))}`;
287
+ return [`${constraint.afterMs ?? 0} ≤ ${variable}`, `${variable} ≤ ${constraint.withinMs}`];
288
+ }
289
+ return [`n${integerVariables.indexOf(transitionKey(constraint))} = ${states.indexOf(constraint.to)}`];
290
+ }),
291
+ ];
292
+ const explicitExpression = propositions.join(' ∧ ') || 'True';
293
+ const existential = [
294
+ ...booleanParameters.map((name) => `${name} : Bool`),
295
+ ...integerParameters.map((name) => `${name} : Int`),
296
+ ].reduceRight((body, declaration) => `∃ ${declaration}, ${body}`, `explicitObligations ${[...booleanParameters, ...integerParameters].join(' ')}`.trim());
297
+ const booleanWitnesses = booleanParameters.map((name) => {
298
+ const index = Number(name.slice(1));
299
+ if (name.startsWith('p'))
300
+ return model.literals.find((entry) => atoms.indexOf(entry.atom) === index)?.positive !== false;
301
+ return constraints.find((entry) => entry.constraint.kind === 'conditional'
302
+ && conditionalScenarios.indexOf(conditionalKey(entry.constraint)) === index)
303
+ ?.constraint.kind === 'conditional'
304
+ ? constraints.find((entry) => entry.constraint.kind === 'conditional'
305
+ && conditionalScenarios.indexOf(conditionalKey(entry.constraint)) === index)
306
+ .constraint.consequenceValue
307
+ : true;
308
+ });
309
+ const integerWitnesses = integerVariables.map((key) => {
310
+ const temporalEntries = constraints.filter((entry) => entry.constraint.kind === 'temporal' && temporalKey(entry.constraint) === key);
311
+ if (temporalEntries.length) {
312
+ return constraints
313
+ .filter((entry) => entry.constraint.kind === 'temporal' && temporalKey(entry.constraint) === key)
314
+ .reduce((maximum, entry) => Math.max(maximum, entry.constraint.afterMs ?? 0), 0);
315
+ }
316
+ const transitionEntry = constraints.find((candidate) => candidate.constraint.kind === 'transition' && transitionKey(candidate.constraint) === key);
317
+ if (transitionEntry) {
318
+ const entry = constraints.find((candidate) => candidate.constraint.kind === 'transition'
319
+ && transitionKey(candidate.constraint) === key);
320
+ return entry?.constraint.kind === 'transition' ? states.indexOf(entry.constraint.to) : 0;
321
+ }
322
+ const entries = constraints.filter((entry) => entry.constraint.kind === 'numeric'
323
+ && normalizeNumeric(entry.constraint).key === key)
324
+ .map((entry) => {
325
+ const constraint = entry.constraint;
326
+ return { operator: constraint.operator, value: normalizeNumeric(constraint).value };
327
+ });
328
+ const candidates = new Set(entries.flatMap((entry) => [entry.value - 1n, entry.value, entry.value + 1n]));
329
+ return [...candidates].find((candidate) => entries.every((entry) => satisfies(candidate, entry.operator, entry.value))) ?? entries[0]?.value ?? 0n;
330
+ });
331
+ const contradictoryAtom = atoms.findIndex((atom) => {
332
+ const values = new Set(model.literals.filter((entry) => entry.atom === atom).map((entry) => entry.positive));
333
+ return values.size > 1;
334
+ });
335
+ const contradictoryConditional = conditionalScenarios.findIndex((scenario) => {
336
+ const values = new Set(constraints
337
+ .filter((entry) => entry.constraint.kind === 'conditional'
338
+ && conditionalKey(entry.constraint) === scenario)
339
+ .map((entry) => entry.constraint.consequenceValue));
340
+ return values.size > 1;
341
+ });
342
+ const contradictoryBoolean = contradictoryAtom >= 0
343
+ ? `p${contradictoryAtom}`
344
+ : contradictoryConditional >= 0 ? `q${contradictoryConditional}` : undefined;
345
+ lines.push('', 'def explicitConstraints : List (String × String) := [', constraints.map((entry) => ` (${JSON.stringify(entry.requirement)}, ${JSON.stringify(JSON.stringify(entry.constraint))})`).join(',\n'), ']', ...(states.length ? [
346
+ '',
347
+ `def transitionStates : List String := [${states.map((state) => JSON.stringify(state)).join(', ')}]`,
348
+ ] : []), '', `def explicitObligations ${declarations} : Prop := ${explicitExpression}`, '', `theorem explicit_constraints_${explicitInconsistent ? 'inconsistent' : 'consistent'} : ${explicitInconsistent ? `¬ (${existential})` : existential} := by`, ...(!explicitInconsistent
349
+ ? [` refine ⟨${[...booleanWitnesses.map(String), ...integerWitnesses].join(', ')}, ?_⟩`, ' simp [explicitObligations]']
350
+ : [
351
+ ' intro h',
352
+ ` rcases h with ⟨${[...booleanParameters, ...integerParameters, 'h'].join(', ')}⟩`,
353
+ contradictoryBoolean
354
+ ? ` cases ${contradictoryBoolean} <;> simp [explicitObligations] at h`
355
+ : ' simp [explicitObligations] at h <;> omega',
356
+ ]));
357
+ }
358
+ return lines.join('\n');
359
+ }
360
+ function artifact(format, path, content) {
361
+ return {
362
+ format,
363
+ path,
364
+ sha256: createHash('sha256').update(content).digest('hex'),
365
+ description: format === 'smt2'
366
+ ? 'SMT-LIB2 consistency problem for the documented explicit constraint model.'
367
+ : 'Lean translation and deterministic checks for the normalized constraint model.',
368
+ };
369
+ }
370
+ async function writeArtifact(root, format, content) {
371
+ const path = format === 'smt2'
372
+ ? '.musubix/cache/formal/requirements.smt2'
373
+ : '.musubix/cache/formal/Requirements.lean';
374
+ await writeText(root, path, `${content}\n`);
375
+ return artifact(format, path, `${content}\n`);
376
+ }
377
+ export async function generateFormalArtifacts(text, root, formats = ['smt2', 'lean']) {
378
+ const model = buildModel(text);
379
+ const artifacts = [];
380
+ if (model.validRequirements && (model.literals.length || model.constraints.length)) {
381
+ for (const format of [...new Set(formats)]) {
382
+ artifacts.push(await writeArtifact(root, format, format === 'smt2' ? generateSmt2(model) : generateLean(model)));
383
+ }
384
+ }
385
+ return {
386
+ valid: model.validRequirements && (model.literals.length > 0 || model.constraints.length > 0),
387
+ consistency: model.consistency,
388
+ literals: model.literals,
389
+ constraints: model.constraints,
390
+ unsupported: model.unsupported,
391
+ diagnostics: model.diagnostics,
392
+ artifacts,
393
+ };
394
+ }
395
+ function commandFor(name, options) {
396
+ if (name === 'z3')
397
+ return [{ name, command: options.z3Command ?? process.env.MUSUBIX3_Z3 ?? 'z3', prefixArgs: [] }];
398
+ const configured = options.leanCommand ?? process.env.MUSUBIX3_LEAN;
399
+ if (configured)
400
+ return [{ name, command: configured, prefixArgs: [] }];
401
+ return [
402
+ { name, command: 'lean', prefixArgs: [] },
403
+ { name, command: 'lake', prefixArgs: ['env', 'lean'] },
404
+ ];
405
+ }
406
+ async function probe(invocation, root, timeoutMs, runner) {
407
+ return runner(invocation.command, [...invocation.prefixArgs, invocation.name === 'z3' ? '-version' : '--version'], { cwd: root, timeoutMs });
408
+ }
409
+ async function findSolver(name, root, timeoutMs, options, runner) {
410
+ let last = { status: 'missing', exitCode: null, stdout: '', stderr: '', durationMs: 0 };
411
+ for (const invocation of commandFor(name, options)) {
412
+ last = await probe(invocation, root, timeoutMs, runner);
413
+ if (last.status !== 'missing')
414
+ return { invocation, probe: last };
415
+ }
416
+ return { probe: last };
417
+ }
418
+ function probeStatus(result) {
419
+ if (result.status === 'missing')
420
+ return 'missing';
421
+ if (result.status === 'timeout')
422
+ return 'timeout';
423
+ if (result.status !== 'completed' || result.exitCode !== 0)
424
+ return 'error';
425
+ return 'available';
426
+ }
427
+ export async function formalDoctor(root, options = {}, runner = runProcess) {
428
+ const timeoutMs = options.timeoutMs ?? 5_000;
429
+ const solvers = [];
430
+ for (const name of ['z3', 'lean']) {
431
+ const found = await findSolver(name, root, timeoutMs, options, runner);
432
+ const status = probeStatus(found.probe);
433
+ const invocation = found.invocation ?? commandFor(name, options)[0];
434
+ const output = (found.probe.stdout + found.probe.stderr).trim();
435
+ solvers.push({
436
+ name,
437
+ available: status === 'available',
438
+ command: [invocation.command, ...invocation.prefixArgs].join(' '),
439
+ version: status === 'available' ? output.split(/\r?\n/, 1)[0] ?? '' : '',
440
+ status,
441
+ output,
442
+ });
443
+ }
444
+ return { available: solvers.some((entry) => entry.available), solvers };
445
+ }
446
+ function initialResult(model, requested) {
447
+ return {
448
+ valid: model.validRequirements && model.consistency === 'consistent' && (model.literals.length > 0 || model.constraints.length > 0),
449
+ abstraction,
450
+ assumptions,
451
+ literals: model.literals,
452
+ constraints: model.constraints,
453
+ unsupported: model.unsupported,
454
+ diagnostics: model.diagnostics,
455
+ consistency: model.consistency,
456
+ solver: {
457
+ requested,
458
+ name: requested,
459
+ command: '',
460
+ version: '',
461
+ status: 'not-requested',
462
+ durationMs: 0,
463
+ output: '',
464
+ },
465
+ };
466
+ }
467
+ function executionStatus(execution) {
468
+ if (execution.status === 'missing')
469
+ return 'missing';
470
+ if (execution.status === 'timeout')
471
+ return 'timeout';
472
+ if (execution.status !== 'completed' || execution.exitCode !== 0)
473
+ return 'error';
474
+ return undefined;
475
+ }
476
+ export async function formalCheck(text, root, solverOrOptions = 'auto', runner = runProcess) {
477
+ const options = typeof solverOrOptions === 'string' ? { solver: solverOrOptions } : solverOrOptions;
478
+ const requested = options.solver ?? 'auto';
479
+ const timeoutMs = options.timeoutMs ?? 12_000;
480
+ const model = buildModel(text);
481
+ const result = initialResult(model, requested);
482
+ if (!model.validRequirements || requested === 'none' || (!model.literals.length && !model.constraints.length)) {
483
+ result.solver.output = !model.validRequirements
484
+ ? 'Invalid requirement syntax; solver not run.'
485
+ : !model.literals.length && !model.constraints.length
486
+ ? 'No supported obligations; solver not run.'
487
+ : 'Solver execution disabled.';
488
+ return result;
489
+ }
490
+ let selected;
491
+ for (const candidate of requested === 'auto' ? ['z3', 'lean'] : [requested]) {
492
+ const found = await findSolver(candidate, root, Math.min(timeoutMs, 5_000), options, runner);
493
+ if (!found.invocation && found.probe.status === 'missing')
494
+ continue;
495
+ if (!found.invocation || probeStatus(found.probe) !== 'available') {
496
+ const invocation = found.invocation ?? commandFor(candidate, options)[0];
497
+ result.solver = {
498
+ requested,
499
+ name: candidate,
500
+ command: [invocation.command, ...invocation.prefixArgs].join(' '),
501
+ version: '',
502
+ status: found.probe.status === 'timeout' ? 'timeout' : 'error',
503
+ durationMs: found.probe.durationMs,
504
+ output: (found.probe.stderr || found.probe.stdout).trim(),
505
+ };
506
+ result.valid = false;
507
+ return result;
508
+ }
509
+ selected = { invocation: found.invocation, probe: found.probe };
510
+ break;
511
+ }
512
+ if (!selected) {
513
+ result.solver = {
514
+ requested,
515
+ name: requested,
516
+ command: '',
517
+ version: '',
518
+ status: 'missing',
519
+ durationMs: 0,
520
+ output: 'No requested solver installed; deterministic checks only.',
521
+ };
522
+ if (requested !== 'auto')
523
+ result.valid = false;
524
+ return result;
525
+ }
526
+ const version = (selected.probe.stdout + selected.probe.stderr).trim().split(/\r?\n/, 1)[0] ?? '';
527
+ const command = [selected.invocation.command, ...selected.invocation.prefixArgs].join(' ');
528
+ if (selected.invocation.name === 'z3') {
529
+ const script = generateSmt2(model);
530
+ const generated = await writeArtifact(root, 'smt2', script);
531
+ const execution = await runner(selected.invocation.command, [...selected.invocation.prefixArgs, '-in', '-smt2', `-T:${Math.max(1, Math.ceil(timeoutMs / 1_000))}`], { cwd: root, timeoutMs, input: `${script}\n` });
532
+ const failed = executionStatus(execution);
533
+ const answer = execution.stdout.trim();
534
+ const status = failed ?? (answer === 'sat' ? 'sat' : answer === 'unsat' ? 'unsat' : 'unknown');
535
+ result.solver = {
536
+ requested,
537
+ name: 'z3',
538
+ command,
539
+ version,
540
+ status,
541
+ durationMs: execution.durationMs,
542
+ output: (execution.stdout + execution.stderr).trim(),
543
+ artifact: generated,
544
+ };
545
+ if (status === 'unsat')
546
+ result.consistency = 'inconsistent';
547
+ if (status === 'sat')
548
+ result.consistency = 'consistent';
549
+ if (status !== 'sat' || model.consistency !== 'consistent')
550
+ result.valid = false;
551
+ if ((status === 'sat' && model.consistency === 'inconsistent') || (status === 'unsat' && model.consistency === 'consistent')) {
552
+ result.diagnostics.push(error('FORMAL_SOLVER_MISMATCH', 'Z3 result disagrees with the deterministic contradiction check.'));
553
+ }
554
+ }
555
+ else {
556
+ const script = generateLean(model);
557
+ const generated = await writeArtifact(root, 'lean', script);
558
+ const execution = await runner(selected.invocation.command, [...selected.invocation.prefixArgs, generated.path], { cwd: root, timeoutMs });
559
+ const failed = executionStatus(execution);
560
+ const status = failed ?? 'checked';
561
+ result.solver = {
562
+ requested,
563
+ name: 'lean',
564
+ command,
565
+ version,
566
+ status,
567
+ durationMs: execution.durationMs,
568
+ output: (execution.stdout + execution.stderr).trim()
569
+ || (status === 'checked'
570
+ ? `Lean checked the ${model.consistency === 'inconsistent' ? 'unsatisfiability' : 'satisfiability'} theorem for the normalized constraint model.`
571
+ : ''),
572
+ artifact: generated,
573
+ };
574
+ if (status !== 'checked')
575
+ result.valid = false;
576
+ }
577
+ return result;
578
+ }
579
+ //# sourceMappingURL=formal.js.map