@orcalang/orca-lang 0.1.17 → 0.1.19
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.
- package/dist/compiler/dt-compiler.d.ts +23 -0
- package/dist/compiler/dt-compiler.d.ts.map +1 -0
- package/dist/compiler/dt-compiler.js +183 -0
- package/dist/compiler/dt-compiler.js.map +1 -0
- package/dist/health-check.d.ts +3 -0
- package/dist/health-check.d.ts.map +1 -0
- package/dist/health-check.js +235 -0
- package/dist/health-check.js.map +1 -0
- package/dist/parser/ast.d.ts +2 -0
- package/dist/parser/ast.d.ts.map +1 -1
- package/dist/parser/dt-ast.d.ts +43 -0
- package/dist/parser/dt-ast.d.ts.map +1 -0
- package/dist/parser/dt-ast.js +3 -0
- package/dist/parser/dt-ast.js.map +1 -0
- package/dist/parser/dt-parser.d.ts +40 -0
- package/dist/parser/dt-parser.d.ts.map +1 -0
- package/dist/parser/dt-parser.js +240 -0
- package/dist/parser/dt-parser.js.map +1 -0
- package/dist/parser/markdown-parser.d.ts.map +1 -1
- package/dist/parser/markdown-parser.js +29 -4
- package/dist/parser/markdown-parser.js.map +1 -1
- package/dist/skills.d.ts +49 -1
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +223 -0
- package/dist/skills.js.map +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +49 -0
- package/dist/tools.js.map +1 -1
- package/dist/verifier/dt-verifier.d.ts +5 -0
- package/dist/verifier/dt-verifier.d.ts.map +1 -0
- package/dist/verifier/dt-verifier.js +499 -0
- package/dist/verifier/dt-verifier.js.map +1 -0
- package/dist/verifier/types.d.ts +4 -0
- package/dist/verifier/types.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/compiler/dt-compiler.ts +232 -0
- package/src/health-check.ts +273 -0
- package/src/parser/ast.ts +3 -0
- package/src/parser/dt-ast.ts +40 -0
- package/src/parser/dt-parser.ts +289 -0
- package/src/parser/markdown-parser.ts +32 -5
- package/src/skills.ts +274 -1
- package/src/tools.ts +53 -0
- package/src/verifier/dt-verifier.ts +562 -0
- package/src/verifier/types.ts +4 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// Decision Table Compiler
|
|
2
|
+
// Compiles verified decision tables to TypeScript evaluator functions or JSON
|
|
3
|
+
|
|
4
|
+
import { DecisionTableDef, CellValue } from '../parser/dt-ast.js';
|
|
5
|
+
|
|
6
|
+
// Convert camelCase or snake_case to PascalCase
|
|
7
|
+
function toPascalCase(name: string): string {
|
|
8
|
+
return name
|
|
9
|
+
.split(/[_-]/)
|
|
10
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
11
|
+
.join('');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Generate TypeScript type for input interface
|
|
15
|
+
function generateInputType(dt: DecisionTableDef): string {
|
|
16
|
+
const lines: string[] = [];
|
|
17
|
+
lines.push(`export interface ${toPascalCase(dt.name)}Input {`);
|
|
18
|
+
|
|
19
|
+
for (const cond of dt.conditions) {
|
|
20
|
+
const typeStr = generateTypeScriptType(cond.type, cond.values);
|
|
21
|
+
lines.push(` ${cond.name}: ${typeStr};`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
lines.push('}');
|
|
25
|
+
return lines.join('\n');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Generate TypeScript type for output interface
|
|
29
|
+
function generateOutputType(dt: DecisionTableDef): string {
|
|
30
|
+
const lines: string[] = [];
|
|
31
|
+
lines.push(`export interface ${toPascalCase(dt.name)}Output {`);
|
|
32
|
+
|
|
33
|
+
for (const action of dt.actions) {
|
|
34
|
+
const typeStr = generateTypeScriptType(action.type, action.values || []);
|
|
35
|
+
lines.push(` ${action.name}: ${typeStr};`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
lines.push('}');
|
|
39
|
+
return lines.join('\n');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Generate TypeScript type string from condition/action type
|
|
43
|
+
function generateTypeScriptType(type: string, values: string[]): string {
|
|
44
|
+
if (type === 'bool') {
|
|
45
|
+
return 'boolean';
|
|
46
|
+
}
|
|
47
|
+
if (type === 'enum') {
|
|
48
|
+
return values.length > 0 ? values.map(v => `'${v}'`).join(' | ') : 'string';
|
|
49
|
+
}
|
|
50
|
+
if (type === 'int_range') {
|
|
51
|
+
return 'number';
|
|
52
|
+
}
|
|
53
|
+
return 'string';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Generate condition check code
|
|
57
|
+
function generateConditionCheck(condName: string, condType: string, cell: CellValue): string {
|
|
58
|
+
switch (cell.kind) {
|
|
59
|
+
case 'any':
|
|
60
|
+
return ''; // No condition needed
|
|
61
|
+
|
|
62
|
+
case 'exact':
|
|
63
|
+
// For bool type, compare to boolean; for others, compare to string
|
|
64
|
+
if (condType === 'bool') {
|
|
65
|
+
return `input.${condName} === ${cell.value}`;
|
|
66
|
+
}
|
|
67
|
+
return `input.${condName} === '${cell.value}'`;
|
|
68
|
+
|
|
69
|
+
case 'negated':
|
|
70
|
+
if (condType === 'bool') {
|
|
71
|
+
return `input.${condName} !== ${cell.value}`;
|
|
72
|
+
}
|
|
73
|
+
return `input.${condName} !== '${cell.value}'`;
|
|
74
|
+
|
|
75
|
+
case 'set':
|
|
76
|
+
const checks = cell.values.map(v => {
|
|
77
|
+
if (condType === 'bool') {
|
|
78
|
+
return `input.${condName} === ${v}`;
|
|
79
|
+
}
|
|
80
|
+
return `input.${condName} === '${v}'`;
|
|
81
|
+
}).join(' || ');
|
|
82
|
+
return `(${checks})`;
|
|
83
|
+
|
|
84
|
+
default:
|
|
85
|
+
return '';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Generate return statement for a rule
|
|
90
|
+
function generateReturnStatement(dt: DecisionTableDef, ruleIndex: number): string {
|
|
91
|
+
const rule = dt.rules[ruleIndex];
|
|
92
|
+
const actionEntries: string[] = [];
|
|
93
|
+
|
|
94
|
+
for (const action of dt.actions) {
|
|
95
|
+
const value = rule.actions.get(action.name);
|
|
96
|
+
if (value !== undefined) {
|
|
97
|
+
if (action.type === 'bool') {
|
|
98
|
+
// Bool actions use true/false
|
|
99
|
+
actionEntries.push(` ${action.name}: ${value}`);
|
|
100
|
+
} else {
|
|
101
|
+
actionEntries.push(` ${action.name}: '${value}'`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return `return {\n${actionEntries.join(',\n')}\n };`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Compile to TypeScript evaluator function
|
|
110
|
+
export function compileDecisionTableToTypeScript(dt: DecisionTableDef): string {
|
|
111
|
+
const inputTypeName = `${toPascalCase(dt.name)}Input`;
|
|
112
|
+
const outputTypeName = `${toPascalCase(dt.name)}Output`;
|
|
113
|
+
const functionName = `evaluate${toPascalCase(dt.name)}`;
|
|
114
|
+
|
|
115
|
+
const lines: string[] = [];
|
|
116
|
+
|
|
117
|
+
// Input interface
|
|
118
|
+
lines.push(generateInputType(dt));
|
|
119
|
+
lines.push('');
|
|
120
|
+
|
|
121
|
+
// Output interface
|
|
122
|
+
lines.push(generateOutputType(dt));
|
|
123
|
+
lines.push('');
|
|
124
|
+
|
|
125
|
+
// Evaluator function
|
|
126
|
+
lines.push(`export function ${functionName}(input: ${inputTypeName}): ${outputTypeName} | null {`);
|
|
127
|
+
|
|
128
|
+
// Generate rule checks
|
|
129
|
+
for (let ruleIdx = 0; ruleIdx < dt.rules.length; ruleIdx++) {
|
|
130
|
+
const rule = dt.rules[ruleIdx];
|
|
131
|
+
const ruleNum = rule.number ?? ruleIdx + 1;
|
|
132
|
+
|
|
133
|
+
// Collect condition checks
|
|
134
|
+
const checks: string[] = [];
|
|
135
|
+
for (const [condName, cell] of rule.conditions) {
|
|
136
|
+
const condDef = dt.conditions.find(c => c.name === condName);
|
|
137
|
+
const condType = condDef?.type ?? 'string';
|
|
138
|
+
const check = generateConditionCheck(condName, condType, cell);
|
|
139
|
+
if (check) {
|
|
140
|
+
checks.push(check);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Build the if statement
|
|
145
|
+
if (checks.length === 0) {
|
|
146
|
+
// Rule with all wildcards - always matches
|
|
147
|
+
lines.push(` // Rule ${ruleNum}: always matches`);
|
|
148
|
+
lines.push(` {`);
|
|
149
|
+
lines.push(` ${generateReturnStatement(dt, ruleIdx)}`);
|
|
150
|
+
lines.push(` }`);
|
|
151
|
+
} else {
|
|
152
|
+
lines.push(` // Rule ${ruleNum}`);
|
|
153
|
+
const conditionStr = checks.join(' && ');
|
|
154
|
+
lines.push(` if (${conditionStr}) {`);
|
|
155
|
+
lines.push(` ${generateReturnStatement(dt, ruleIdx)}`);
|
|
156
|
+
lines.push(` }`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Default return null
|
|
161
|
+
lines.push(' return null; // no rule matched');
|
|
162
|
+
lines.push('}');
|
|
163
|
+
|
|
164
|
+
return lines.join('\n');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Compile to JSON
|
|
168
|
+
export interface DTJSONRule {
|
|
169
|
+
conditions: Record<string, string>;
|
|
170
|
+
actions: Record<string, string>;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export interface DTJSON {
|
|
174
|
+
name: string;
|
|
175
|
+
conditions: Array<{
|
|
176
|
+
name: string;
|
|
177
|
+
type: string;
|
|
178
|
+
values: string[];
|
|
179
|
+
}>;
|
|
180
|
+
actions: Array<{
|
|
181
|
+
name: string;
|
|
182
|
+
type: string;
|
|
183
|
+
values?: string[];
|
|
184
|
+
}>;
|
|
185
|
+
rules: DTJSONRule[];
|
|
186
|
+
policy: 'first-match' | 'all-match';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function compileDecisionTableToJSON(dt: DecisionTableDef): string {
|
|
190
|
+
const json: DTJSON = {
|
|
191
|
+
name: dt.name,
|
|
192
|
+
conditions: dt.conditions.map(c => ({
|
|
193
|
+
name: c.name,
|
|
194
|
+
type: c.type,
|
|
195
|
+
values: c.values,
|
|
196
|
+
})),
|
|
197
|
+
actions: dt.actions.map(a => {
|
|
198
|
+
const result: { name: string; type: string; values?: string[] } = {
|
|
199
|
+
name: a.name,
|
|
200
|
+
type: a.type,
|
|
201
|
+
};
|
|
202
|
+
if (a.values) {
|
|
203
|
+
result.values = a.values;
|
|
204
|
+
}
|
|
205
|
+
return result;
|
|
206
|
+
}),
|
|
207
|
+
rules: dt.rules.map(rule => {
|
|
208
|
+
// Omit wildcard conditions (kind: 'any')
|
|
209
|
+
const conditions: Record<string, string> = {};
|
|
210
|
+
for (const [name, cell] of rule.conditions) {
|
|
211
|
+
if (cell.kind === 'exact') {
|
|
212
|
+
conditions[name] = cell.value;
|
|
213
|
+
} else if (cell.kind === 'negated') {
|
|
214
|
+
conditions[name] = `!${cell.value}`;
|
|
215
|
+
} else if (cell.kind === 'set') {
|
|
216
|
+
conditions[name] = cell.values.join(',');
|
|
217
|
+
}
|
|
218
|
+
// 'any' is omitted
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const actions: Record<string, string> = {};
|
|
222
|
+
for (const [name, value] of rule.actions) {
|
|
223
|
+
actions[name] = value;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return { conditions, actions };
|
|
227
|
+
}),
|
|
228
|
+
policy: dt.policy,
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
return JSON.stringify(json, null, 2);
|
|
232
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Health Check Runner - Dogfooding Orca to check itself
|
|
3
|
+
// Uses sequential execution pattern modeled after Orca state machine semantics
|
|
4
|
+
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
|
+
import { join, dirname } from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const REPO_ROOT = join(__dirname, '../../..');
|
|
11
|
+
|
|
12
|
+
interface StepResult {
|
|
13
|
+
name: string;
|
|
14
|
+
status: 'pending' | 'success' | 'failed';
|
|
15
|
+
output: string;
|
|
16
|
+
duration: number;
|
|
17
|
+
passed?: number;
|
|
18
|
+
skipped?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface HealthReport {
|
|
22
|
+
steps: StepResult[];
|
|
23
|
+
startTime: number;
|
|
24
|
+
endTime: number;
|
|
25
|
+
totalPassed: number;
|
|
26
|
+
totalFailed: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function runCommand(cmd: string, cwd: string, timeout = 120000): { status: number; stdout: string; stderr: string } {
|
|
30
|
+
try {
|
|
31
|
+
const stdout = execSync(cmd, { cwd, encoding: 'utf-8', timeout, maxBuffer: 10 * 1024 * 1024 });
|
|
32
|
+
return { status: 0, stdout: stdout as string, stderr: '' };
|
|
33
|
+
} catch (e: any) {
|
|
34
|
+
return {
|
|
35
|
+
status: e.status || 1,
|
|
36
|
+
stdout: e.stdout?.toString() || '',
|
|
37
|
+
stderr: e.stderr?.toString() || e.message || ''
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function runHealthCheck(): Promise<HealthReport> {
|
|
43
|
+
const report: HealthReport = {
|
|
44
|
+
steps: [],
|
|
45
|
+
startTime: Date.now(),
|
|
46
|
+
endTime: 0,
|
|
47
|
+
totalPassed: 0,
|
|
48
|
+
totalFailed: 0
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// ── Step 1: Build ──────────────────────────────────────────────
|
|
52
|
+
{
|
|
53
|
+
const step: StepResult = { name: 'build', status: 'pending', output: '', duration: 0 };
|
|
54
|
+
const start = Date.now();
|
|
55
|
+
console.log('━━━ Building TypeScript packages ━━━');
|
|
56
|
+
const result = runCommand('pnpm run build', REPO_ROOT);
|
|
57
|
+
step.duration = Date.now() - start;
|
|
58
|
+
step.status = result.status === 0 ? 'success' : 'failed';
|
|
59
|
+
step.output = result.status === 0 ? 'Build succeeded' : result.stderr || result.stdout;
|
|
60
|
+
report.steps.push(step);
|
|
61
|
+
console.log(` ${step.status === 'success' ? '✓' : '✗'} Build ${step.status} (${step.duration}ms)\n`);
|
|
62
|
+
if (step.status === 'failed') {
|
|
63
|
+
report.endTime = Date.now();
|
|
64
|
+
return report;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ── Step 2: orca-lang tests ─────────────────────────────────────
|
|
69
|
+
{
|
|
70
|
+
const step: StepResult = { name: 'test:lang', status: 'pending', output: '', duration: 0 };
|
|
71
|
+
const start = Date.now();
|
|
72
|
+
console.log('━━━ Running orca-lang tests ━━━');
|
|
73
|
+
const result = runCommand('cd packages/orca-lang && pnpm test 2>&1', REPO_ROOT);
|
|
74
|
+
step.duration = Date.now() - start;
|
|
75
|
+
const match = result.stdout.match(/(\d+) passed.*?(\d+) skipped/);
|
|
76
|
+
step.passed = match ? parseInt(match[1]) : 0;
|
|
77
|
+
step.skipped = match ? parseInt(match[2]) : 0;
|
|
78
|
+
step.status = result.status === 0 && step.passed >= 180 ? 'success' : 'failed';
|
|
79
|
+
step.output = `${step.passed} passed, ${step.skipped} skipped`;
|
|
80
|
+
report.steps.push(step);
|
|
81
|
+
console.log(` ${step.status === 'success' ? '✓' : '✗'} ${step.passed} tests passed, ${step.skipped} skipped (${step.duration}ms)\n`);
|
|
82
|
+
if (step.status === 'failed') {
|
|
83
|
+
report.endTime = Date.now();
|
|
84
|
+
return report;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── Step 3: demo-ts smoke test ─────────────────────────────────
|
|
89
|
+
{
|
|
90
|
+
const step: StepResult = { name: 'demo-ts:test', status: 'pending', output: '', duration: 0 };
|
|
91
|
+
const start = Date.now();
|
|
92
|
+
console.log('━━━ Running demo-ts smoke test ━━━');
|
|
93
|
+
const result = runCommand('pnpm --filter orca-demo-ts run test 2>&1', REPO_ROOT);
|
|
94
|
+
step.duration = Date.now() - start;
|
|
95
|
+
step.status = result.status === 0 ? 'success' : 'failed';
|
|
96
|
+
step.output = result.status === 0 ? 'Tests passed' : result.stdout;
|
|
97
|
+
report.steps.push(step);
|
|
98
|
+
console.log(` ${step.status === 'success' ? '✓' : '✗'} demo-ts tests ${step.status} (${step.duration}ms)\n`);
|
|
99
|
+
if (step.status === 'failed') {
|
|
100
|
+
report.endTime = Date.now();
|
|
101
|
+
return report;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── Step 4: demo-ts ticket demo ─────────────────────────────────
|
|
106
|
+
{
|
|
107
|
+
const step: StepResult = { name: 'demo-ts:ticket', status: 'pending', output: '', duration: 0 };
|
|
108
|
+
const start = Date.now();
|
|
109
|
+
console.log('━━━ Running demo-ts ticket demo ━━━');
|
|
110
|
+
const result = runCommand('pnpm -w run run:demo-ts:ticket 2>&1', REPO_ROOT);
|
|
111
|
+
step.duration = Date.now() - start;
|
|
112
|
+
step.status = result.status === 0 ? 'success' : 'failed';
|
|
113
|
+
step.output = result.status === 0 ? 'Demo passed' : result.stdout;
|
|
114
|
+
report.steps.push(step);
|
|
115
|
+
console.log(` ${step.status === 'success' ? '✓' : '✗'} Support Ticket demo ${step.status} (${step.duration}ms)\n`);
|
|
116
|
+
if (step.status === 'failed') {
|
|
117
|
+
report.endTime = Date.now();
|
|
118
|
+
return report;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── Step 5: demo-python ─────────────────────────────────────────
|
|
123
|
+
{
|
|
124
|
+
const step: StepResult = { name: 'demo-python', status: 'pending', output: '', duration: 0 };
|
|
125
|
+
const start = Date.now();
|
|
126
|
+
console.log('━━━ Running demo-python ━━━');
|
|
127
|
+
const result = runCommand('.venv/bin/python packages/demo-python/demo.py 2>&1', REPO_ROOT);
|
|
128
|
+
step.duration = Date.now() - start;
|
|
129
|
+
step.status = result.status === 0 ? 'success' : 'failed';
|
|
130
|
+
step.output = result.status === 0 ? 'Demo passed' : result.stdout;
|
|
131
|
+
report.steps.push(step);
|
|
132
|
+
console.log(` ${step.status === 'success' ? '✓' : '✗'} demo-python ${step.status} (${step.duration}ms)\n`);
|
|
133
|
+
if (step.status === 'failed') {
|
|
134
|
+
report.endTime = Date.now();
|
|
135
|
+
return report;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ── Step 6: demo-go ─────────────────────────────────────────────
|
|
140
|
+
{
|
|
141
|
+
const step: StepResult = { name: 'demo-go (trip)', status: 'pending', output: '', duration: 0 };
|
|
142
|
+
const start = Date.now();
|
|
143
|
+
console.log('━━━ Running demo-go (trip) ━━━');
|
|
144
|
+
const result = runCommand('pnpm run test:demo-go 2>&1', REPO_ROOT);
|
|
145
|
+
step.duration = Date.now() - start;
|
|
146
|
+
step.status = result.status === 0 ? 'success' : 'failed';
|
|
147
|
+
step.output = result.status === 0 ? 'Demo passed' : result.stdout;
|
|
148
|
+
report.steps.push(step);
|
|
149
|
+
console.log(` ${step.status === 'success' ? '✓' : '✗'} demo-go (trip) ${step.status} (${step.duration}ms)\n`);
|
|
150
|
+
if (step.status === 'failed') {
|
|
151
|
+
report.endTime = Date.now();
|
|
152
|
+
return report;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ── Step 6b: demo-go:loan ──────────────────────────────────────
|
|
157
|
+
{
|
|
158
|
+
const step: StepResult = { name: 'demo-go:loan', status: 'pending', output: '', duration: 0 };
|
|
159
|
+
const start = Date.now();
|
|
160
|
+
|
|
161
|
+
// Build the loan binary first (not pre-built like 'trip')
|
|
162
|
+
console.log('━━━ Building demo-go:loan binary ━━━');
|
|
163
|
+
const buildResult = runCommand('cd packages/demo-go && go build -o loan ./cmd/loan 2>&1', REPO_ROOT);
|
|
164
|
+
if (buildResult.status !== 0) {
|
|
165
|
+
step.duration = Date.now() - start;
|
|
166
|
+
step.status = 'failed';
|
|
167
|
+
step.output = buildResult.stderr || buildResult.stdout;
|
|
168
|
+
report.steps.push(step);
|
|
169
|
+
console.log(` ✗ Build failed (${step.duration}ms): ${step.output}\n`);
|
|
170
|
+
report.endTime = Date.now();
|
|
171
|
+
return report;
|
|
172
|
+
}
|
|
173
|
+
console.log(` ✓ Build succeeded (${Date.now() - start}ms)\n`);
|
|
174
|
+
|
|
175
|
+
console.log('━━━ Running demo-go:loan (loan application) ━━━');
|
|
176
|
+
const runStart = Date.now();
|
|
177
|
+
const result = runCommand('cd packages/demo-go && ./loan 2>&1', REPO_ROOT);
|
|
178
|
+
step.duration = Date.now() - runStart;
|
|
179
|
+
step.status = result.status === 0 ? 'success' : 'failed';
|
|
180
|
+
step.output = result.status === 0 ? 'Demo passed' : result.stdout;
|
|
181
|
+
report.steps.push(step);
|
|
182
|
+
console.log(` ${step.status === 'success' ? '✓' : '✗'} demo-go:loan (loan) ${step.status} (${step.duration}ms)\n`);
|
|
183
|
+
if (step.status === 'failed') {
|
|
184
|
+
report.endTime = Date.now();
|
|
185
|
+
return report;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ── Step 8: demo-nanolab tests ─────────────────────────────────
|
|
190
|
+
{
|
|
191
|
+
const step: StepResult = { name: 'demo-nanolab:test', status: 'pending', output: '', duration: 0 };
|
|
192
|
+
const start = Date.now();
|
|
193
|
+
console.log('━━━ Running demo-nanolab tests ━━━');
|
|
194
|
+
const result = runCommand('pnpm run test:demo-nanolab 2>&1', REPO_ROOT);
|
|
195
|
+
step.duration = Date.now() - start;
|
|
196
|
+
step.status = result.status === 0 ? 'success' : 'failed';
|
|
197
|
+
step.output = result.status === 0 ? 'Tests passed' : result.stdout;
|
|
198
|
+
report.steps.push(step);
|
|
199
|
+
console.log(` ${step.status === 'success' ? '✓' : '✗'} demo-nanolab tests ${step.status} (${step.duration}ms)\n`);
|
|
200
|
+
if (step.status === 'failed') {
|
|
201
|
+
report.endTime = Date.now();
|
|
202
|
+
return report;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ── Step 8: Example verification ────────────────────────────────
|
|
207
|
+
{
|
|
208
|
+
const step: StepResult = { name: 'examples:verify', status: 'pending', output: '', duration: 0 };
|
|
209
|
+
const start = Date.now();
|
|
210
|
+
console.log('━━━ Verifying example files ━━━');
|
|
211
|
+
const result = runCommand('cd packages/orca-lang && pnpm exec tsx src/index.ts verify examples/simple-toggle.orca.md examples/payment-processor.orca.md 2>&1', REPO_ROOT);
|
|
212
|
+
step.duration = Date.now() - start;
|
|
213
|
+
step.status = result.status === 0 ? 'success' : 'failed';
|
|
214
|
+
step.output = result.status === 0 ? 'Examples verified' : result.stdout;
|
|
215
|
+
report.steps.push(step);
|
|
216
|
+
console.log(` ${step.status === 'success' ? '✓' : '✗'} Examples verified ${step.status} (${step.duration}ms)\n`);
|
|
217
|
+
if (step.status === 'failed') {
|
|
218
|
+
report.endTime = Date.now();
|
|
219
|
+
return report;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
report.endTime = Date.now();
|
|
224
|
+
return report;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function printReport(report: HealthReport) {
|
|
228
|
+
console.log('╔════════════════════════════════════════════════════════════╗');
|
|
229
|
+
console.log('║ HEALTH CHECK REPORT ║');
|
|
230
|
+
console.log('╚════════════════════════════════════════════════════════════╝\n');
|
|
231
|
+
|
|
232
|
+
for (const step of report.steps) {
|
|
233
|
+
const icon = step.status === 'success' ? '✓' : step.status === 'failed' ? '✗' : '○';
|
|
234
|
+
const label = step.status === 'success' ? 'PASS' : step.status === 'failed' ? 'FAIL' : 'SKIP';
|
|
235
|
+
console.log(` ${icon} ${step.name.padEnd(20)} ${label.padEnd(6)} ${step.duration}ms${step.passed !== undefined ? ` (${step.passed} tests)` : ''}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const totalDuration = report.endTime - report.startTime;
|
|
239
|
+
report.totalPassed = report.steps.filter(s => s.status === 'success').length;
|
|
240
|
+
report.totalFailed = report.steps.filter(s => s.status === 'failed').length;
|
|
241
|
+
|
|
242
|
+
console.log(`\n Total time: ${totalDuration}ms`);
|
|
243
|
+
console.log(` Passed: ${report.totalPassed}/${report.totalPassed + report.totalFailed}\n`);
|
|
244
|
+
|
|
245
|
+
if (report.totalFailed === 0) {
|
|
246
|
+
console.log(' ✓ ALL CHECKS PASSED - Project is healthy\n');
|
|
247
|
+
} else {
|
|
248
|
+
console.log(` ✗ ${report.totalFailed} CHECK(S) FAILED\n`);
|
|
249
|
+
// Print failed step outputs
|
|
250
|
+
for (const step of report.steps.filter(s => s.status === 'failed')) {
|
|
251
|
+
console.log(` ── ${step.name} output ──`);
|
|
252
|
+
console.log(` ${step.output.split('\n').slice(0, 10).join('\n ')}`);
|
|
253
|
+
if (step.output.split('\n').length > 10) console.log(' ... (truncated)');
|
|
254
|
+
console.log();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function main() {
|
|
260
|
+
console.log('╔════════════════════════════════════════════════════════════╗');
|
|
261
|
+
console.log('║ ORCA HEALTH CHECK - SELF-HOSTED RUNNER ║');
|
|
262
|
+
console.log('╚════════════════════════════════════════════════════════════╝\n');
|
|
263
|
+
|
|
264
|
+
const report = await runHealthCheck();
|
|
265
|
+
printReport(report);
|
|
266
|
+
|
|
267
|
+
process.exit(report.totalFailed > 0 ? 1 : 0);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
main().catch(err => {
|
|
271
|
+
console.error('Health check failed with error:', err);
|
|
272
|
+
process.exit(1);
|
|
273
|
+
});
|
package/src/parser/ast.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// Orca AST Type Definitions
|
|
2
2
|
|
|
3
|
+
import { DecisionTableDef } from './dt-ast.js';
|
|
4
|
+
|
|
3
5
|
export interface Position {
|
|
4
6
|
line: number;
|
|
5
7
|
column: number;
|
|
@@ -228,6 +230,7 @@ export interface MachineDef {
|
|
|
228
230
|
// Multi-machine file (for machine invocation)
|
|
229
231
|
export interface OrcaFile {
|
|
230
232
|
machines: MachineDef[];
|
|
233
|
+
decisionTables: DecisionTableDef[]; // NEW
|
|
231
234
|
}
|
|
232
235
|
|
|
233
236
|
export interface ParseResult {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Decision Table AST Types
|
|
2
|
+
|
|
3
|
+
export type ConditionType = 'bool' | 'enum' | 'int_range' | 'string';
|
|
4
|
+
|
|
5
|
+
export interface ConditionDef {
|
|
6
|
+
name: string;
|
|
7
|
+
type: ConditionType;
|
|
8
|
+
values: string[]; // enum values, or ['true','false'] for bool
|
|
9
|
+
range?: { min: number; max: number }; // for int_range
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type ActionType = 'bool' | 'enum' | 'string';
|
|
13
|
+
|
|
14
|
+
export interface ActionOutputDef {
|
|
15
|
+
name: string;
|
|
16
|
+
type: ActionType;
|
|
17
|
+
description?: string;
|
|
18
|
+
values?: string[]; // valid values for enum type
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type CellValue =
|
|
22
|
+
| { kind: 'any' } // "-" wildcard
|
|
23
|
+
| { kind: 'exact'; value: string } // exact match
|
|
24
|
+
| { kind: 'negated'; value: string } // "!value"
|
|
25
|
+
| { kind: 'set'; values: string[] }; // "a,b" (match any in set)
|
|
26
|
+
|
|
27
|
+
export interface Rule {
|
|
28
|
+
number?: number; // optional rule # from the # column
|
|
29
|
+
conditions: Map<string, CellValue>; // condition name → cell value
|
|
30
|
+
actions: Map<string, string>; // action name → output value
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface DecisionTableDef {
|
|
34
|
+
name: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
conditions: ConditionDef[];
|
|
37
|
+
actions: ActionOutputDef[];
|
|
38
|
+
rules: Rule[];
|
|
39
|
+
policy: 'first-match' | 'all-match'; // default: first-match
|
|
40
|
+
}
|