progmune-runtime 2.1.1 → 2.1.2
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/emitter.js +44 -2
- package/dist/health-utils.js +40 -0
- package/dist/ledger-utils.js +40 -0
- package/dist/stdlib.js +205 -0
- package/package.json +1 -1
package/dist/emitter.js
CHANGED
|
@@ -152,13 +152,19 @@ function emitCode(actions, meta) {
|
|
|
152
152
|
else if (action.kind === "assign" && action.target) {
|
|
153
153
|
declared.add(action.target);
|
|
154
154
|
}
|
|
155
|
-
// Collect arg value references
|
|
155
|
+
// Collect arg value references AND empty-default params
|
|
156
156
|
if (action.kind === "call" && action.args) {
|
|
157
157
|
for (const arg of action.args) {
|
|
158
158
|
const v = typeof arg === "object" ? arg?.value : arg;
|
|
159
159
|
if (typeof v === "string" && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(v) && v !== "") {
|
|
160
160
|
referenced.add(v);
|
|
161
161
|
}
|
|
162
|
+
// Also detect empty defaults: these should become function params
|
|
163
|
+
const isDefault = v === "" || v === 0 || v === false || v === null
|
|
164
|
+
|| (Array.isArray(v) && v.length === 0);
|
|
165
|
+
if (isDefault && typeof arg === "object" && arg.name) {
|
|
166
|
+
referenced.add(arg.name);
|
|
167
|
+
}
|
|
162
168
|
}
|
|
163
169
|
}
|
|
164
170
|
}
|
|
@@ -169,10 +175,40 @@ function emitCode(actions, meta) {
|
|
|
169
175
|
if (action.kind === "call" && action.args) {
|
|
170
176
|
for (const arg of action.args) {
|
|
171
177
|
const v = typeof arg === "object" ? arg?.value : arg;
|
|
178
|
+
const n = typeof arg === "object" ? arg?.name : undefined;
|
|
172
179
|
const t = typeof arg === "object" ? arg?.type : "string";
|
|
180
|
+
// Variable reference: value matches input name
|
|
173
181
|
if (typeof v === "string" && inputs.includes(v) && t && t !== "any") {
|
|
174
182
|
inputTypes.set(v, t);
|
|
175
183
|
}
|
|
184
|
+
// Empty default: param name matches input name
|
|
185
|
+
const isDefault = v === "" || v === 0 || v === false || v === null
|
|
186
|
+
|| (Array.isArray(v) && v.length === 0);
|
|
187
|
+
if (isDefault && n && inputs.includes(n)) {
|
|
188
|
+
const cleanType = (t || "string").replace(/\[\]$/, "");
|
|
189
|
+
if (!inputTypes.has(n))
|
|
190
|
+
inputTypes.set(n, cleanType);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// If no inputs detected but functions have params with empty defaults,
|
|
196
|
+
// create parameters from function signatures
|
|
197
|
+
if (inputs.length === 0) {
|
|
198
|
+
for (const action of actions) {
|
|
199
|
+
if (action.kind === "call" && action.args && action.args.length > 0) {
|
|
200
|
+
for (const arg of action.args) {
|
|
201
|
+
const name = typeof arg === "object" ? arg.name : "param";
|
|
202
|
+
const type = typeof arg === "object" ? (arg.type || "string") : "string";
|
|
203
|
+
const val = typeof arg === "object" ? arg.value : arg;
|
|
204
|
+
// Only add if value is empty default (not a real value)
|
|
205
|
+
if (val === "" || val === 0 || val === false || val === null || (Array.isArray(val) && val.length === 0)) {
|
|
206
|
+
if (!inputTypes.has(name)) {
|
|
207
|
+
inputs.push(name);
|
|
208
|
+
inputTypes.set(name, type.replace(/\[\]$/, ""));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
176
212
|
}
|
|
177
213
|
}
|
|
178
214
|
}
|
|
@@ -192,11 +228,17 @@ function emitCode(actions, meta) {
|
|
|
192
228
|
const val = a?.value;
|
|
193
229
|
if (typeof val === "string" && declared.has(val))
|
|
194
230
|
return val;
|
|
195
|
-
// If value looks like a variable name (valid JS identifier), pass it through
|
|
196
231
|
if (typeof val === "string" && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(val) && val !== "") {
|
|
197
232
|
declared.add(val);
|
|
198
233
|
return val;
|
|
199
234
|
}
|
|
235
|
+
// Empty default value → use function's parameter name (input parameter)
|
|
236
|
+
const isDefault = val === "" || val === 0 || val === false || val === null
|
|
237
|
+
|| (Array.isArray(val) && val.length === 0);
|
|
238
|
+
if (isDefault && a?.name && inputs.includes(a.name)) {
|
|
239
|
+
declared.add(a.name);
|
|
240
|
+
return a.name;
|
|
241
|
+
}
|
|
200
242
|
const paramType = meta?.params?.[i]?.type || "any";
|
|
201
243
|
if (BASIC_TYPES.has(paramType)) {
|
|
202
244
|
if (paramType === "string" || paramType === "str")
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.computeHealthScore = computeHealthScore;
|
|
4
|
+
exports.formatHealthLevel = formatHealthLevel;
|
|
5
|
+
exports.countSessionLedgers = countSessionLedgers;
|
|
6
|
+
/** Compute overall immune health score from failure and antibody data.
|
|
7
|
+
* @requires FAILURE_GENOME @produces HEALTH_SCORE
|
|
8
|
+
* @tags health, score, immune
|
|
9
|
+
*/
|
|
10
|
+
function computeHealthScore(failureGenome, antibodyStats) {
|
|
11
|
+
const totalFailures = failureGenome?.totalFailures || 0;
|
|
12
|
+
const totalHits = antibodyStats?.totalHits || 0;
|
|
13
|
+
const base = 100;
|
|
14
|
+
const failurePenalty = Math.min(totalFailures * 2, 40);
|
|
15
|
+
const antibodyBonus = Math.min(totalHits * 3, 20);
|
|
16
|
+
return Math.max(0, Math.min(100, base - failurePenalty + antibodyBonus));
|
|
17
|
+
}
|
|
18
|
+
/** Format a health score as a status level.
|
|
19
|
+
* @requires HEALTH_SCORE @produces HEALTH_STATUS
|
|
20
|
+
* @tags health, format
|
|
21
|
+
*/
|
|
22
|
+
function formatHealthLevel(score) {
|
|
23
|
+
if (score >= 90)
|
|
24
|
+
return "Excellent";
|
|
25
|
+
if (score >= 70)
|
|
26
|
+
return "Good";
|
|
27
|
+
if (score >= 50)
|
|
28
|
+
return "Fair";
|
|
29
|
+
return "Poor";
|
|
30
|
+
}
|
|
31
|
+
/** Validate a ledger and return pass/fail counts.
|
|
32
|
+
* @requires SESSION_LIST @produces VALIDATION_COUNTS
|
|
33
|
+
* @tags ledger, validation, audit
|
|
34
|
+
*/
|
|
35
|
+
function countSessionLedgers(sessions) {
|
|
36
|
+
const withLedger = sessions.filter((s) => {
|
|
37
|
+
return s.attempts?.some((a) => a.transitions?.length > 0);
|
|
38
|
+
}).length;
|
|
39
|
+
return { total: sessions.length, withLedger };
|
|
40
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.countTotalTransitions = countTotalTransitions;
|
|
4
|
+
exports.formatTransitionCount = formatTransitionCount;
|
|
5
|
+
exports.hasViolations = hasViolations;
|
|
6
|
+
exports.countSessionsWithViolations = countSessionsWithViolations;
|
|
7
|
+
/** Count total transitions across all session ledgers.
|
|
8
|
+
* @requires SESSION_LIST @produces TRANSITION_COUNT
|
|
9
|
+
* @tags ledger, count, statistics
|
|
10
|
+
*/
|
|
11
|
+
function countTotalTransitions(sessions) {
|
|
12
|
+
let count = 0;
|
|
13
|
+
for (const s of sessions) {
|
|
14
|
+
for (const a of (s.attempts || [])) {
|
|
15
|
+
count += (a.transitions || []).length;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return count;
|
|
19
|
+
}
|
|
20
|
+
/** Format a transition count as a summary string.
|
|
21
|
+
* @requires TRANSITION_COUNT @produces FORMATTED_COUNT
|
|
22
|
+
* @tags ledger, format
|
|
23
|
+
*/
|
|
24
|
+
function formatTransitionCount(count) {
|
|
25
|
+
return `${count} total transitions across all sessions`;
|
|
26
|
+
}
|
|
27
|
+
/** Check if a session has any protocol violations in its attempts.
|
|
28
|
+
* @requires SESSION_DATA @produces VIOLATION_CHECK
|
|
29
|
+
* @tags ledger, validation
|
|
30
|
+
*/
|
|
31
|
+
function hasViolations(session) {
|
|
32
|
+
return (session.attempts || []).some((a) => (a.violations || []).length > 0);
|
|
33
|
+
}
|
|
34
|
+
/** Count sessions that have violations.
|
|
35
|
+
* @requires SESSION_LIST @produces VIOLATION_COUNT
|
|
36
|
+
* @tags ledger, validation, statistics
|
|
37
|
+
*/
|
|
38
|
+
function countSessionsWithViolations(sessions) {
|
|
39
|
+
return sessions.filter(s => hasViolations(s)).length;
|
|
40
|
+
}
|
package/dist/stdlib.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Progmune Standard Library — general-purpose utilities for external tasks.
|
|
4
|
+
* Each function has @requires/@produces for Capability Graph integration.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.isValidEmail = isValidEmail;
|
|
8
|
+
exports.truncate = truncate;
|
|
9
|
+
exports.camelToSnake = camelToSnake;
|
|
10
|
+
exports.capitalizeWords = capitalizeWords;
|
|
11
|
+
exports.countSubstring = countSubstring;
|
|
12
|
+
exports.removeWhitespace = removeWhitespace;
|
|
13
|
+
exports.mostFrequent = mostFrequent;
|
|
14
|
+
exports.unique = unique;
|
|
15
|
+
exports.chunk = chunk;
|
|
16
|
+
exports.arrayDiff = arrayDiff;
|
|
17
|
+
exports.average = average;
|
|
18
|
+
exports.median = median;
|
|
19
|
+
exports.roundTo = roundTo;
|
|
20
|
+
exports.isPrime = isPrime;
|
|
21
|
+
exports.randomInt = randomInt;
|
|
22
|
+
exports.deepClone = deepClone;
|
|
23
|
+
exports.pick = pick;
|
|
24
|
+
exports.deepMerge = deepMerge;
|
|
25
|
+
exports.hasRequiredFields = hasRequiredFields;
|
|
26
|
+
exports.isPlainObject = isPlainObject;
|
|
27
|
+
exports.parseSemver = parseSemver;
|
|
28
|
+
exports.formatDuration = formatDuration;
|
|
29
|
+
exports.formatFileSize = formatFileSize;
|
|
30
|
+
exports.toQueryString = toQueryString;
|
|
31
|
+
exports.retry = retry;
|
|
32
|
+
exports.debounce = debounce;
|
|
33
|
+
// ── String ──
|
|
34
|
+
/** @requires STRING @produces VALIDATION_RESULT @tags string, email, validation */
|
|
35
|
+
function isValidEmail(str) {
|
|
36
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str);
|
|
37
|
+
}
|
|
38
|
+
/** @requires STRING @produces TRUNCATED_STRING @tags string, format */
|
|
39
|
+
function truncate(str, maxLen, ellipsis = "...") {
|
|
40
|
+
return str.length <= maxLen ? str : str.slice(0, maxLen - ellipsis.length) + ellipsis;
|
|
41
|
+
}
|
|
42
|
+
/** @requires STRING @produces FORMATTED_STRING @tags string, case, format */
|
|
43
|
+
function camelToSnake(str) {
|
|
44
|
+
return str.replace(/[A-Z]/g, (c) => "_" + c.toLowerCase());
|
|
45
|
+
}
|
|
46
|
+
/** @requires STRING @produces FORMATTED_STRING @tags string, case, format */
|
|
47
|
+
function capitalizeWords(str) {
|
|
48
|
+
return str.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
49
|
+
}
|
|
50
|
+
/** @requires STRING @produces COUNT @tags string, count */
|
|
51
|
+
function countSubstring(str, sub) {
|
|
52
|
+
if (!sub)
|
|
53
|
+
return 0;
|
|
54
|
+
let count = 0, pos = 0;
|
|
55
|
+
while ((pos = str.indexOf(sub, pos)) !== -1) {
|
|
56
|
+
count++;
|
|
57
|
+
pos += sub.length;
|
|
58
|
+
}
|
|
59
|
+
return count;
|
|
60
|
+
}
|
|
61
|
+
/** @requires STRING @produces CLEANED_STRING @tags string, format */
|
|
62
|
+
function removeWhitespace(str) {
|
|
63
|
+
return str.replace(/\s+/g, "");
|
|
64
|
+
}
|
|
65
|
+
// ── Array ──
|
|
66
|
+
/** @requires ARRAY @produces ELEMENT @tags array, statistics */
|
|
67
|
+
function mostFrequent(arr) {
|
|
68
|
+
if (arr.length === 0)
|
|
69
|
+
return null;
|
|
70
|
+
const counts = new Map();
|
|
71
|
+
for (const item of arr)
|
|
72
|
+
counts.set(item, (counts.get(item) || 0) + 1);
|
|
73
|
+
return [...counts.entries()].sort((a, b) => b[1] - a[1])[0][0];
|
|
74
|
+
}
|
|
75
|
+
/** @requires ARRAY @produces ARRAY @tags array, dedupe */
|
|
76
|
+
function unique(arr) { return [...new Set(arr)]; }
|
|
77
|
+
/** @requires ARRAY @produces ARRAY @tags array, chunk */
|
|
78
|
+
function chunk(arr, size) {
|
|
79
|
+
const result = [];
|
|
80
|
+
for (let i = 0; i < arr.length; i += size)
|
|
81
|
+
result.push(arr.slice(i, i + size));
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
/** @requires ARRAY @produces ARRAY @tags array, difference */
|
|
85
|
+
function arrayDiff(a, b) {
|
|
86
|
+
const setB = new Set(b);
|
|
87
|
+
return a.filter(x => !setB.has(x));
|
|
88
|
+
}
|
|
89
|
+
// ── Math ──
|
|
90
|
+
/** @requires NUMBERS @produces AVERAGE @tags math, statistics */
|
|
91
|
+
function average(nums) {
|
|
92
|
+
return nums.length === 0 ? 0 : nums.reduce((a, b) => a + b, 0) / nums.length;
|
|
93
|
+
}
|
|
94
|
+
/** @requires NUMBERS @produces MEDIAN @tags math, statistics */
|
|
95
|
+
function median(nums) {
|
|
96
|
+
if (nums.length === 0)
|
|
97
|
+
return 0;
|
|
98
|
+
const sorted = [...nums].sort((a, b) => a - b);
|
|
99
|
+
const mid = Math.floor(sorted.length / 2);
|
|
100
|
+
return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
|
101
|
+
}
|
|
102
|
+
/** @requires NUMBER @produces ROUNDED_NUMBER @tags math, format */
|
|
103
|
+
function roundTo(num, decimals) {
|
|
104
|
+
const factor = Math.pow(10, decimals);
|
|
105
|
+
return Math.round(num * factor) / factor;
|
|
106
|
+
}
|
|
107
|
+
/** @requires NUMBER @produces PRIME_CHECK @tags math, validation */
|
|
108
|
+
function isPrime(n) {
|
|
109
|
+
if (n < 2)
|
|
110
|
+
return false;
|
|
111
|
+
for (let i = 2; i <= Math.sqrt(n); i++)
|
|
112
|
+
if (n % i === 0)
|
|
113
|
+
return false;
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
/** @requires NUMBERS @produces RANDOM_INT @tags math, random */
|
|
117
|
+
function randomInt(min, max) {
|
|
118
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
119
|
+
}
|
|
120
|
+
// ── Object ──
|
|
121
|
+
/** @requires OBJECT @produces CLONED_OBJECT @tags object, clone */
|
|
122
|
+
function deepClone(obj) {
|
|
123
|
+
return JSON.parse(JSON.stringify(obj));
|
|
124
|
+
}
|
|
125
|
+
/** @requires OBJECT @produces PICKED_OBJECT @tags object, filter */
|
|
126
|
+
function pick(obj, keys) {
|
|
127
|
+
const result = {};
|
|
128
|
+
for (const k of keys)
|
|
129
|
+
if (k in obj)
|
|
130
|
+
result[k] = obj[k];
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
/** @requires OBJECTS @produces MERGED_OBJECT @tags object, merge */
|
|
134
|
+
function deepMerge(...objects) {
|
|
135
|
+
return objects.reduce((acc, obj) => {
|
|
136
|
+
for (const key of Object.keys(obj || {})) {
|
|
137
|
+
acc[key] = typeof obj[key] === "object" && !Array.isArray(obj[key])
|
|
138
|
+
? deepMerge(acc[key] || {}, obj[key]) : obj[key];
|
|
139
|
+
}
|
|
140
|
+
return acc;
|
|
141
|
+
}, {});
|
|
142
|
+
}
|
|
143
|
+
// ── Validation ──
|
|
144
|
+
/** @requires OBJECT @produces VALIDATION_RESULT @tags validation, schema */
|
|
145
|
+
function hasRequiredFields(obj, fields) {
|
|
146
|
+
return fields.every(f => obj && obj[f] !== undefined && obj[f] !== null);
|
|
147
|
+
}
|
|
148
|
+
/** @requires ANY @produces VALIDATION_RESULT @tags validation, type */
|
|
149
|
+
function isPlainObject(val) {
|
|
150
|
+
return val !== null && typeof val === "object" && !Array.isArray(val);
|
|
151
|
+
}
|
|
152
|
+
/** @requires STRING @produces PARSED_VERSION @tags validation, semver */
|
|
153
|
+
function parseSemver(version) {
|
|
154
|
+
const match = version.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
155
|
+
if (!match)
|
|
156
|
+
return null;
|
|
157
|
+
return { major: +match[1], minor: +match[2], patch: +match[3] };
|
|
158
|
+
}
|
|
159
|
+
// ── Formatting ──
|
|
160
|
+
/** @requires NUMBER @produces FORMATTED_STRING @tags format, duration */
|
|
161
|
+
function formatDuration(ms) {
|
|
162
|
+
if (ms < 1000)
|
|
163
|
+
return `${ms}ms`;
|
|
164
|
+
if (ms < 60000)
|
|
165
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
166
|
+
const mins = Math.floor(ms / 60000);
|
|
167
|
+
const secs = Math.round((ms % 60000) / 1000);
|
|
168
|
+
return `${mins}m ${secs}s`;
|
|
169
|
+
}
|
|
170
|
+
/** @requires NUMBER @produces FORMATTED_STRING @tags format, file */
|
|
171
|
+
function formatFileSize(bytes) {
|
|
172
|
+
if (bytes < 1024)
|
|
173
|
+
return `${bytes}B`;
|
|
174
|
+
if (bytes < 1048576)
|
|
175
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
176
|
+
return `${(bytes / 1048576).toFixed(1)}MB`;
|
|
177
|
+
}
|
|
178
|
+
/** @requires OBJECT @produces QUERY_STRING @tags web, format */
|
|
179
|
+
function toQueryString(obj) {
|
|
180
|
+
return Object.entries(obj)
|
|
181
|
+
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
|
|
182
|
+
.join("&");
|
|
183
|
+
}
|
|
184
|
+
// ── Async ──
|
|
185
|
+
/** @requires FUNCTION @produces RETRY_RESULT @tags async, retry */
|
|
186
|
+
async function retry(fn, maxRetries = 3) {
|
|
187
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
188
|
+
try {
|
|
189
|
+
return await fn();
|
|
190
|
+
}
|
|
191
|
+
catch (e) {
|
|
192
|
+
if (i === maxRetries - 1)
|
|
193
|
+
throw e;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
throw new Error("unreachable");
|
|
197
|
+
}
|
|
198
|
+
/** @requires FUNCTION @produces DEBOUNCED_FUNCTION @tags async, debounce */
|
|
199
|
+
function debounce(fn, delay) {
|
|
200
|
+
let timer;
|
|
201
|
+
return ((...args) => {
|
|
202
|
+
clearTimeout(timer);
|
|
203
|
+
timer = setTimeout(() => fn(...args), delay);
|
|
204
|
+
});
|
|
205
|
+
}
|