@eigenpal/cli 0.5.8 → 0.5.9
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/CHANGELOG.md +5 -1
- package/dist/cli.js +228 -20
- package/dist/skill/reference/cli/workflow.md +18 -12
- package/dist/skill/reference/workflow-yaml.md +70 -0
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @eigenpal/cli
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 0.5.9
|
|
4
4
|
|
|
5
5
|
### Major Changes
|
|
6
6
|
|
|
@@ -333,6 +333,10 @@ actual) { ... }` declaration around your statements. Parameter order is
|
|
|
333
333
|
|
|
334
334
|
Also scrubs internal references from CLI source ahead of open-sourcing — generic placeholder names in docstrings and test fixtures, internal-tracker IDs stripped from comments, and one user-facing error message updated to point at the public issue tracker.
|
|
335
335
|
|
|
336
|
+
- 75caaec: `eigenpal workflow push` and `eigenpal workflow validate` now surface non-fatal **schema-quality warnings** when an output schema is loose enough to hurt downstream typing: categorical fields like `category`/`status`/`kind` declared as plain strings (no `enum`), `transform.script` functions returning `any`/`unknown`, untyped objects (`type: object` with no `properties`), and untyped arrays (`type: array` with no `items`). Warnings print to stderr and never block the push.
|
|
337
|
+
|
|
338
|
+
Updated the bundled CLI skill (`workflow-yaml.md`) with a "Be specific with types" section teaching `enum` fields, per-value meanings inline in `description`, and how `transform.script` TS literal unions auto-produce JSON Schema enums.
|
|
339
|
+
|
|
336
340
|
- be693a3: `EIGENPAL_API_KEY` is now a complete profile bypass — when set, the active profile's `baseUrl` is also ignored (previously the env key was paired with the profile's URL, which is always wrong because an API key is provisioned for one server).
|
|
337
341
|
|
|
338
342
|
In CI:
|
package/dist/cli.js
CHANGED
|
@@ -28107,7 +28107,8 @@ var init_parser = __esm(() => {
|
|
|
28107
28107
|
maxConcurrency: exports_external.number().min(1).max(30).default(3),
|
|
28108
28108
|
pagesPerBatch: exports_external.number().min(1).max(20).default(5),
|
|
28109
28109
|
prompt: exports_external.string().optional().describe("Custom prompt for LLM-based parsing"),
|
|
28110
|
-
languages: exports_external.array(exports_external.string()).optional().describe("OCR language hints")
|
|
28110
|
+
languages: exports_external.array(exports_external.string()).optional().describe("OCR language hints"),
|
|
28111
|
+
tenantId: exports_external.string().optional()
|
|
28111
28112
|
});
|
|
28112
28113
|
ParserInputSchema = exports_external.object({
|
|
28113
28114
|
data: exports_external.instanceof(Buffer).or(exports_external.instanceof(Uint8Array)),
|
|
@@ -65788,8 +65789,16 @@ function convertTsTypeToJsonSchema(node, ctx) {
|
|
|
65788
65789
|
case "TSVoidKeyword":
|
|
65789
65790
|
return {};
|
|
65790
65791
|
case "TSAnyKeyword":
|
|
65791
|
-
case "TSUnknownKeyword":
|
|
65792
|
+
case "TSUnknownKeyword": {
|
|
65793
|
+
const keyword = node.type === "TSAnyKeyword" ? "any" : "unknown";
|
|
65794
|
+
ctx.warnings.push({
|
|
65795
|
+
code: "weak-return-type",
|
|
65796
|
+
message: `\`${keyword}\` produces an empty schema, so downstream steps can't autocomplete this field or its descendants. Describe the actual shape (\`{ name: string; total: number }\`) or a literal union (\`"low" | "medium" | "high"\`) for the strongest downstream typing.`,
|
|
65797
|
+
start: node.start ?? undefined,
|
|
65798
|
+
end: node.end ?? undefined
|
|
65799
|
+
});
|
|
65792
65800
|
return {};
|
|
65801
|
+
}
|
|
65793
65802
|
case "TSNeverKeyword":
|
|
65794
65803
|
return { not: {} };
|
|
65795
65804
|
case "TSArrayType":
|
|
@@ -66138,13 +66147,15 @@ function compileTypedScript(opts) {
|
|
|
66138
66147
|
});
|
|
66139
66148
|
}
|
|
66140
66149
|
const convertIssues = [];
|
|
66150
|
+
const convertWarnings = [];
|
|
66141
66151
|
const inScopeSchemas = new Map(Object.entries(opts.inScopeSchemas ?? {}));
|
|
66142
66152
|
const localAliases = collectLocalAliases(aliasDecls);
|
|
66143
66153
|
const returnSchema = opts.kind === "evaluator" ? { type: "number" } : convertTsTypeToJsonSchema(returnTsType, {
|
|
66144
66154
|
localAliases,
|
|
66145
66155
|
inScopeSchemas,
|
|
66146
66156
|
resolving: new Set,
|
|
66147
|
-
issues: convertIssues
|
|
66157
|
+
issues: convertIssues,
|
|
66158
|
+
warnings: convertWarnings
|
|
66148
66159
|
});
|
|
66149
66160
|
if (convertIssues.length > 0) {
|
|
66150
66161
|
return {
|
|
@@ -66157,10 +66168,7 @@ function compileTypedScript(opts) {
|
|
|
66157
66168
|
}
|
|
66158
66169
|
let stripped;
|
|
66159
66170
|
try {
|
|
66160
|
-
|
|
66161
|
-
transforms: ["typescript"]
|
|
66162
|
-
});
|
|
66163
|
-
stripped = out.code;
|
|
66171
|
+
stripped = import_sucrase.transform(opts.source, { transforms: ["typescript"] }).code;
|
|
66164
66172
|
} catch (err) {
|
|
66165
66173
|
const e2 = err;
|
|
66166
66174
|
return failWith({
|
|
@@ -66174,7 +66182,8 @@ function compileTypedScript(opts) {
|
|
|
66174
66182
|
js: stripped,
|
|
66175
66183
|
returnSchema,
|
|
66176
66184
|
paramNames: actualParamNames
|
|
66177
|
-
}
|
|
66185
|
+
},
|
|
66186
|
+
warnings: convertWarnings
|
|
66178
66187
|
};
|
|
66179
66188
|
}
|
|
66180
66189
|
function failWith(issue2) {
|
|
@@ -181601,7 +181610,7 @@ import { fileURLToPath as fileURLToPath6 } from "url";
|
|
|
181601
181610
|
// package.json
|
|
181602
181611
|
var package_default = {
|
|
181603
181612
|
name: "@eigenpal/cli",
|
|
181604
|
-
version: "0.5.
|
|
181613
|
+
version: "0.5.9",
|
|
181605
181614
|
description: "Eigenpal CLI — author and run document workflows from your terminal. Agent-ready.",
|
|
181606
181615
|
keywords: [
|
|
181607
181616
|
"eigenpal",
|
|
@@ -181641,6 +181650,8 @@ var package_default = {
|
|
|
181641
181650
|
scripts: {
|
|
181642
181651
|
dev: "bun src/cli.ts",
|
|
181643
181652
|
build: "bun run generate:skill && bun build src/cli.ts --outdir dist --target node --format esm && rm -rf dist/templates dist/skill && cp -R src/templates dist/templates && cp -R src/skill dist/skill",
|
|
181653
|
+
generate: "bun run generate:cli-docs && bun run generate:skill",
|
|
181654
|
+
"generate:check": "bun run generate:cli-docs:check && bun run generate:skill:check",
|
|
181644
181655
|
"generate:skill": "bun scripts/generate-skill-reference.ts",
|
|
181645
181656
|
"generate:skill:check": "bun scripts/generate-skill-reference.ts --check",
|
|
181646
181657
|
"generate:cli-docs": "bun scripts/generate-cli-docs.ts",
|
|
@@ -188043,6 +188054,155 @@ function parseWorkflow(yaml) {
|
|
|
188043
188054
|
}
|
|
188044
188055
|
return result.data;
|
|
188045
188056
|
}
|
|
188057
|
+
// ../workflow-yaml/src/validate-quality.ts
|
|
188058
|
+
init_src();
|
|
188059
|
+
var CATEGORICAL_NAMES = new Set([
|
|
188060
|
+
"category",
|
|
188061
|
+
"subcategory",
|
|
188062
|
+
"kind",
|
|
188063
|
+
"status",
|
|
188064
|
+
"type",
|
|
188065
|
+
"subtype",
|
|
188066
|
+
"level",
|
|
188067
|
+
"severity",
|
|
188068
|
+
"priority",
|
|
188069
|
+
"state",
|
|
188070
|
+
"stage",
|
|
188071
|
+
"tier",
|
|
188072
|
+
"classification",
|
|
188073
|
+
"monitoring",
|
|
188074
|
+
"frequency",
|
|
188075
|
+
"reference",
|
|
188076
|
+
"sectionname",
|
|
188077
|
+
"code",
|
|
188078
|
+
"flag",
|
|
188079
|
+
"signal",
|
|
188080
|
+
"mode",
|
|
188081
|
+
"phase"
|
|
188082
|
+
]);
|
|
188083
|
+
var SEPARATOR_LIST_RE = /(?:^|[\s(])((?:[A-Za-z0-9_-]{1,20})(?:\s*[|,/]\s*[A-Za-z0-9_-]{1,20}){1,7})(?:$|[\s).])/;
|
|
188084
|
+
function validateSchemaQuality(workflow2) {
|
|
188085
|
+
const warnings = [];
|
|
188086
|
+
for (let i5 = 0;i5 < workflow2.steps.length; i5++) {
|
|
188087
|
+
const step = workflow2.steps[i5];
|
|
188088
|
+
if (!step)
|
|
188089
|
+
continue;
|
|
188090
|
+
const basePath = `steps.${i5}`;
|
|
188091
|
+
walkStep(step, basePath, warnings);
|
|
188092
|
+
}
|
|
188093
|
+
return warnings;
|
|
188094
|
+
}
|
|
188095
|
+
function walkStep(step, basePath, out) {
|
|
188096
|
+
if (step.type === "ai.extract") {
|
|
188097
|
+
const schema = step.with?.schema;
|
|
188098
|
+
if (schema && typeof schema === "object") {
|
|
188099
|
+
walkSchema(schema, `${basePath}.with.schema`, out);
|
|
188100
|
+
}
|
|
188101
|
+
return;
|
|
188102
|
+
}
|
|
188103
|
+
if (step.type === "transform.script") {
|
|
188104
|
+
const cfg = step.with;
|
|
188105
|
+
const source2 = typeof cfg?.function === "string" ? cfg.function : null;
|
|
188106
|
+
if (!source2)
|
|
188107
|
+
return;
|
|
188108
|
+
const paramNames = cfg?.inputs && typeof cfg.inputs === "object" && !Array.isArray(cfg.inputs) ? Object.keys(cfg.inputs) : [];
|
|
188109
|
+
const compiled = compileTypedScript({ kind: "transform", source: source2, paramNames });
|
|
188110
|
+
if (!compiled.ok)
|
|
188111
|
+
return;
|
|
188112
|
+
for (const w10 of compiled.warnings) {
|
|
188113
|
+
if (w10.code === "weak-return-type") {
|
|
188114
|
+
out.push({
|
|
188115
|
+
code: "weak-script-return",
|
|
188116
|
+
field: `${basePath}.with.function`,
|
|
188117
|
+
message: `transform.script return type is too loose: ${w10.message}`,
|
|
188118
|
+
hint: 'Replace `unknown`/`any` with a concrete shape, or use a TS literal union (e.g. `"low" | "medium" | "high"`) when the value belongs to a closed set.'
|
|
188119
|
+
});
|
|
188120
|
+
}
|
|
188121
|
+
}
|
|
188122
|
+
const schema = compiled.result.returnSchema;
|
|
188123
|
+
if (schema.type === "object" && (!schema.properties || Object.keys(schema.properties).length === 0) && schema.additionalProperties !== false) {
|
|
188124
|
+
out.push({
|
|
188125
|
+
code: "untyped-object",
|
|
188126
|
+
field: `${basePath}.with.function`,
|
|
188127
|
+
message: "transform.script returns an untyped object. Downstream steps cannot autocomplete its fields.",
|
|
188128
|
+
hint: "Describe the actual shape in the return annotation: `function script(...): { name: string; total: number }`."
|
|
188129
|
+
});
|
|
188130
|
+
}
|
|
188131
|
+
return;
|
|
188132
|
+
}
|
|
188133
|
+
const withAny = step.with;
|
|
188134
|
+
const nestedSteps = withAny?.steps;
|
|
188135
|
+
if (Array.isArray(nestedSteps)) {
|
|
188136
|
+
for (let i5 = 0;i5 < nestedSteps.length; i5++) {
|
|
188137
|
+
const s5 = nestedSteps[i5];
|
|
188138
|
+
if (s5)
|
|
188139
|
+
walkStep(s5, `${basePath}.with.steps.${i5}`, out);
|
|
188140
|
+
}
|
|
188141
|
+
}
|
|
188142
|
+
const nestedBranches = withAny?.branches;
|
|
188143
|
+
if (Array.isArray(nestedBranches)) {
|
|
188144
|
+
for (let b8 = 0;b8 < nestedBranches.length; b8++) {
|
|
188145
|
+
const branch = nestedBranches[b8];
|
|
188146
|
+
if (branch && Array.isArray(branch.steps)) {
|
|
188147
|
+
for (let i5 = 0;i5 < branch.steps.length; i5++) {
|
|
188148
|
+
const s5 = branch.steps[i5];
|
|
188149
|
+
if (s5)
|
|
188150
|
+
walkStep(s5, `${basePath}.with.branches.${b8}.steps.${i5}`, out);
|
|
188151
|
+
}
|
|
188152
|
+
}
|
|
188153
|
+
}
|
|
188154
|
+
}
|
|
188155
|
+
}
|
|
188156
|
+
function walkSchema(schema, basePath, out) {
|
|
188157
|
+
const type = schema.type;
|
|
188158
|
+
if (type === "object" && (!schema.properties || Object.keys(schema.properties).length === 0) && schema.additionalProperties !== false) {
|
|
188159
|
+
out.push({
|
|
188160
|
+
code: "untyped-object",
|
|
188161
|
+
field: basePath,
|
|
188162
|
+
message: "Object schema has no properties. The LLM has no guidance on what fields to emit and downstream autocomplete is empty.",
|
|
188163
|
+
hint: "Add concrete fields under `properties`, or set `additionalProperties: false` if the object is intentionally open."
|
|
188164
|
+
});
|
|
188165
|
+
}
|
|
188166
|
+
if (type === "array" && (schema.items === undefined || isEmptyObject(schema.items))) {
|
|
188167
|
+
out.push({
|
|
188168
|
+
code: "untyped-array-items",
|
|
188169
|
+
field: basePath,
|
|
188170
|
+
message: "Array items are untyped. The LLM does not know what shape each element should take.",
|
|
188171
|
+
hint: "Add an `items:` block describing the element type, e.g. `items: { type: object, properties: { ... } }`."
|
|
188172
|
+
});
|
|
188173
|
+
}
|
|
188174
|
+
if (schema.properties && typeof schema.properties === "object") {
|
|
188175
|
+
for (const [name, raw] of Object.entries(schema.properties)) {
|
|
188176
|
+
if (!raw || typeof raw !== "object")
|
|
188177
|
+
continue;
|
|
188178
|
+
const prop = raw;
|
|
188179
|
+
const propType = prop.type;
|
|
188180
|
+
const propEnum = Array.isArray(prop.enum) && prop.enum.length > 0;
|
|
188181
|
+
const lower = name.toLowerCase();
|
|
188182
|
+
const description = typeof prop.description === "string" ? prop.description : "";
|
|
188183
|
+
const looksCategoricalByName = CATEGORICAL_NAMES.has(lower);
|
|
188184
|
+
const looksCategoricalByDescription = description.length > 0 && SEPARATOR_LIST_RE.test(description);
|
|
188185
|
+
if (propType === "string" && !propEnum && (looksCategoricalByName || looksCategoricalByDescription) && !looksLikeFreeform(prop)) {
|
|
188186
|
+
out.push({
|
|
188187
|
+
code: "categorical-missing-enum",
|
|
188188
|
+
field: `${basePath}.properties.${name}`,
|
|
188189
|
+
message: `Field \`${name}\` looks categorical but has no \`enum\`. Coded values extract more reliably when the LLM is constrained to a closed set.`,
|
|
188190
|
+
hint: "In the workflow builder, switch the field type to `Enum` and list the allowed values. In raw YAML, add `enum: ['value1', 'value2', ...]` to the field, and write the per-value meanings into the field's `description`."
|
|
188191
|
+
});
|
|
188192
|
+
}
|
|
188193
|
+
walkSchema(prop, `${basePath}.properties.${name}`, out);
|
|
188194
|
+
}
|
|
188195
|
+
}
|
|
188196
|
+
if (schema.items && typeof schema.items === "object" && !Array.isArray(schema.items)) {
|
|
188197
|
+
walkSchema(schema.items, `${basePath}.items`, out);
|
|
188198
|
+
}
|
|
188199
|
+
}
|
|
188200
|
+
function isEmptyObject(v11) {
|
|
188201
|
+
return typeof v11 === "object" && v11 !== null && !Array.isArray(v11) && Object.keys(v11).length === 0;
|
|
188202
|
+
}
|
|
188203
|
+
function looksLikeFreeform(prop) {
|
|
188204
|
+
return prop.pattern !== undefined || prop.format !== undefined;
|
|
188205
|
+
}
|
|
188046
188206
|
// src/commands/validate.ts
|
|
188047
188207
|
var import_yaml6 = __toESM(require_dist(), 1);
|
|
188048
188208
|
import { existsSync as existsSync10, readdirSync as readdirSync7, readFileSync as readFileSync10, statSync as statSync6 } from "fs";
|
|
@@ -188061,24 +188221,46 @@ function isDirSafe(path15, issues, field) {
|
|
|
188061
188221
|
}
|
|
188062
188222
|
function validateWorkflowYaml(path15) {
|
|
188063
188223
|
if (!existsSync10(path15)) {
|
|
188064
|
-
return [{ field: path15, message: "File not found." }];
|
|
188224
|
+
return { issues: [{ field: path15, message: "File not found." }], warnings: [] };
|
|
188065
188225
|
}
|
|
188066
188226
|
const text = readFileSync10(path15, "utf-8");
|
|
188067
188227
|
try {
|
|
188068
|
-
parseWorkflow(text);
|
|
188069
|
-
|
|
188228
|
+
const definition = parseWorkflow(text);
|
|
188229
|
+
let warnings = [];
|
|
188230
|
+
try {
|
|
188231
|
+
warnings = validateSchemaQuality(definition);
|
|
188232
|
+
} catch {
|
|
188233
|
+
warnings = [];
|
|
188234
|
+
}
|
|
188235
|
+
return { issues: [], warnings };
|
|
188070
188236
|
} catch (err2) {
|
|
188071
188237
|
if (err2 instanceof YamlParseError) {
|
|
188072
188238
|
const where = err2.line !== undefined ? ` (line ${err2.line})` : "";
|
|
188073
|
-
return [{ field: "yaml", message: `${err2.message}${where}` }];
|
|
188239
|
+
return { issues: [{ field: "yaml", message: `${err2.message}${where}` }], warnings: [] };
|
|
188074
188240
|
}
|
|
188075
188241
|
if (err2 instanceof WorkflowValidationError) {
|
|
188076
|
-
return
|
|
188077
|
-
|
|
188078
|
-
|
|
188079
|
-
|
|
188242
|
+
return {
|
|
188243
|
+
issues: err2.errors.map((e8) => ({
|
|
188244
|
+
field: e8.path.length > 0 ? e8.path.map(String).join(".") : "(root)",
|
|
188245
|
+
message: e8.message
|
|
188246
|
+
})),
|
|
188247
|
+
warnings: []
|
|
188248
|
+
};
|
|
188080
188249
|
}
|
|
188081
|
-
return
|
|
188250
|
+
return {
|
|
188251
|
+
issues: [{ field: "(root)", message: err2 instanceof Error ? err2.message : String(err2) }],
|
|
188252
|
+
warnings: []
|
|
188253
|
+
};
|
|
188254
|
+
}
|
|
188255
|
+
}
|
|
188256
|
+
function printSchemaQualityWarnings(path15, warnings) {
|
|
188257
|
+
if (warnings.length === 0)
|
|
188258
|
+
return;
|
|
188259
|
+
warn(`workflow.yaml ${ui.dim(`(${path15})`)}: ${warnings.length} schema-quality warning${warnings.length === 1 ? "" : "s"}`);
|
|
188260
|
+
for (const w10 of warnings) {
|
|
188261
|
+
warn(` ${ui.dim(w10.field)} ${w10.message}`);
|
|
188262
|
+
if (w10.hint)
|
|
188263
|
+
warn(` ${ui.dim("hint:")} ${w10.hint}`);
|
|
188082
188264
|
}
|
|
188083
188265
|
}
|
|
188084
188266
|
function validateEvaluatorsYaml(path15) {
|
|
@@ -188261,9 +188443,23 @@ function rootDir(opts) {
|
|
|
188261
188443
|
return resolve6(opts.dir ?? process.cwd());
|
|
188262
188444
|
}
|
|
188263
188445
|
function registerAllValidateCommand(parent) {
|
|
188264
|
-
parent.command("validate").description("Local-only validation
|
|
188446
|
+
parent.command("validate [path]").description("Local-only validation. Without [path]: runs the templated three-way check (./workflow.yaml + ./evaluators.yaml + ./dataset/). With [path] pointing at a YAML file: validates just that workflow.yaml. For per-noun targeting use `evaluators validate` or `dataset validate`.").option("--dir <path>", "Project root (defaults to cwd; resolves the three default paths from here)").action((path15, opts) => {
|
|
188447
|
+
if (path15) {
|
|
188448
|
+
const target = resolve6(path15);
|
|
188449
|
+
const wfResult2 = validateWorkflowYaml(target);
|
|
188450
|
+
const wfOk2 = printIssues("workflow.yaml", target, wfResult2.issues);
|
|
188451
|
+
if (wfOk2)
|
|
188452
|
+
printSchemaQualityWarnings(target, wfResult2.warnings);
|
|
188453
|
+
if (!wfOk2)
|
|
188454
|
+
process.exit(1);
|
|
188455
|
+
return;
|
|
188456
|
+
}
|
|
188265
188457
|
const root2 = rootDir(opts);
|
|
188266
|
-
const
|
|
188458
|
+
const wfResult = validateWorkflowYaml(join12(root2, "workflow.yaml"));
|
|
188459
|
+
const wfPath = relPath(root2, join12(root2, "workflow.yaml"));
|
|
188460
|
+
const wfOk = printIssues("workflow.yaml", wfPath, wfResult.issues);
|
|
188461
|
+
if (wfOk)
|
|
188462
|
+
printSchemaQualityWarnings(wfPath, wfResult.warnings);
|
|
188267
188463
|
const evOk = printIssues("evaluators.yaml", relPath(root2, join12(root2, "evaluators.yaml")), validateEvaluatorsYaml(join12(root2, "evaluators.yaml")));
|
|
188268
188464
|
const dsOk = printIssues("dataset/", relPath(root2, join12(root2, "dataset")), validateDatasetFolder(join12(root2, "dataset")));
|
|
188269
188465
|
if (!wfOk || !evOk || !dsOk)
|
|
@@ -188916,6 +189112,7 @@ flag would otherwise shadow it. Validation failures come back as
|
|
|
188916
189112
|
return;
|
|
188917
189113
|
}
|
|
188918
189114
|
success2(`Pushed ${ui.bold(workflowId)} (v${result.version ?? nextVersion})`);
|
|
189115
|
+
printSchemaQualityWarnings2(result.warnings);
|
|
188919
189116
|
} else {
|
|
188920
189117
|
if (opts.bump) {
|
|
188921
189118
|
throw new Error("--bump requires --workflow-id (need a current version to bump from). For a new workflow, use --set-version <X.Y.Z> or omit both.");
|
|
@@ -188933,6 +189130,7 @@ flag would otherwise shadow it. Validation failures come back as
|
|
|
188933
189130
|
const id2 = result.workflow?.id ?? result.id ?? "?";
|
|
188934
189131
|
const ver = result.version?.version ?? result.workflow?.currentVersion?.version ?? result.currentVersion?.version ?? "?";
|
|
188935
189132
|
success2(`Created ${ui.bold(id2)} (v${ver})`);
|
|
189133
|
+
printSchemaQualityWarnings2(result.warnings);
|
|
188936
189134
|
}
|
|
188937
189135
|
}));
|
|
188938
189136
|
const moveCmd = parent.command("move <workflow-id>").description("Move a workflow to a folder path, creating folders as needed").requiredOption("--folder <path>", "Target folder path (`/` for root)");
|
|
@@ -188950,6 +189148,16 @@ flag would otherwise shadow it. Validation failures come back as
|
|
|
188950
189148
|
}));
|
|
188951
189149
|
}
|
|
188952
189150
|
var SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)$/;
|
|
189151
|
+
function printSchemaQualityWarnings2(warnings) {
|
|
189152
|
+
if (!warnings || warnings.length === 0)
|
|
189153
|
+
return;
|
|
189154
|
+
warn(`${warnings.length} schema-quality warning${warnings.length === 1 ? "" : "s"} (push succeeded; sharper types make downstream steps easier to wire):`);
|
|
189155
|
+
for (const w10 of warnings) {
|
|
189156
|
+
warn(` ${ui.dim(w10.field)} ${w10.message}`);
|
|
189157
|
+
if (w10.hint)
|
|
189158
|
+
warn(` ${ui.dim("hint:")} ${w10.hint}`);
|
|
189159
|
+
}
|
|
189160
|
+
}
|
|
188953
189161
|
function isBumpLevel(value) {
|
|
188954
189162
|
return value === "patch" || value === "minor" || value === "major";
|
|
188955
189163
|
}
|
|
@@ -20,7 +20,7 @@ Manage workflows: push, pull, run, evaluate.
|
|
|
20
20
|
- [`eigenpal workflow pull [options] <workflow-id>`](#eigenpal-workflow-pull-options-workflow-id)
|
|
21
21
|
- [`eigenpal workflow push [options]`](#eigenpal-workflow-push-options)
|
|
22
22
|
- [`eigenpal workflow move [options] <workflow-id>`](#eigenpal-workflow-move-options-workflow-id)
|
|
23
|
-
- [`eigenpal workflow validate [options]`](#eigenpal-workflow-validate-options)
|
|
23
|
+
- [`eigenpal workflow validate [options] [path]`](#eigenpal-workflow-validate-options-path)
|
|
24
24
|
- [`eigenpal workflow clear-local [options] [examples...]`](#eigenpal-workflow-clear-local-options-examples)
|
|
25
25
|
- [`eigenpal workflow evaluators pull [options] <workflow-id>`](#eigenpal-workflow-evaluators-pull-options-workflow-id)
|
|
26
26
|
- [`eigenpal workflow evaluators push [options] <workflow-id>`](#eigenpal-workflow-evaluators-push-options-workflow-id)
|
|
@@ -100,7 +100,7 @@ workflow
|
|
|
100
100
|
├── evaluator-type
|
|
101
101
|
│ ├── list|ls
|
|
102
102
|
│ └── get <type>
|
|
103
|
-
├── validate
|
|
103
|
+
├── validate [path]
|
|
104
104
|
├── clear-local [examples...]
|
|
105
105
|
└── step
|
|
106
106
|
└── exec <type>
|
|
@@ -110,14 +110,14 @@ workflow
|
|
|
110
110
|
|
|
111
111
|
### Core
|
|
112
112
|
|
|
113
|
-
| Command | Description
|
|
114
|
-
| ------------------------------------------------------- |
|
|
115
|
-
| `eigenpal workflow list | ls [options]`
|
|
116
|
-
| `eigenpal workflow pull [options] <workflow-id>` | Download the YAML definition of the workflow at its current version.
|
|
117
|
-
| `eigenpal workflow push [options]` | Create or update a workflow from a YAML file.
|
|
118
|
-
| `eigenpal workflow move [options] <workflow-id>` | Move a workflow to a folder path, creating folders as needed
|
|
119
|
-
| `eigenpal workflow validate [options]`
|
|
120
|
-
| `eigenpal workflow clear-local [options] [examples...]` | Delete local execution artifacts under ./dataset/examples/. Keeps the latest run per example by default.
|
|
113
|
+
| Command | Description |
|
|
114
|
+
| ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- |
|
|
115
|
+
| `eigenpal workflow list | ls [options]` | List workflows the caller can read. |
|
|
116
|
+
| `eigenpal workflow pull [options] <workflow-id>` | Download the YAML definition of the workflow at its current version. |
|
|
117
|
+
| `eigenpal workflow push [options]` | Create or update a workflow from a YAML file. |
|
|
118
|
+
| `eigenpal workflow move [options] <workflow-id>` | Move a workflow to a folder path, creating folders as needed |
|
|
119
|
+
| `eigenpal workflow validate [options] [path]` | Local-only validation. Without [path]: runs the templated three-way check (./workflow.yaml + ./evaluators.yaml + ./dataset/). With [path] pointing at a YAML file: validates just that workflow.yaml. For per-noun targeting use `evaluators validate` or `dataset validate`. |
|
|
120
|
+
| `eigenpal workflow clear-local [options] [examples...]` | Delete local execution artifacts under ./dataset/examples/. Keeps the latest run per example by default. |
|
|
121
121
|
|
|
122
122
|
### Evaluators
|
|
123
123
|
|
|
@@ -256,9 +256,15 @@ Move a workflow to a folder path, creating folders as needed
|
|
|
256
256
|
| `--base-url <url>` | no | | Server base URL |
|
|
257
257
|
| `--json` | no | | Output the raw server response as JSON |
|
|
258
258
|
|
|
259
|
-
### `eigenpal workflow validate [options]`
|
|
259
|
+
### `eigenpal workflow validate [options] [path]`
|
|
260
260
|
|
|
261
|
-
Local-only validation
|
|
261
|
+
Local-only validation. Without [path]: runs the templated three-way check (./workflow.yaml + ./evaluators.yaml + ./dataset/). With [path] pointing at a YAML file: validates just that workflow.yaml. For per-noun targeting use `evaluators validate` or `dataset validate`.
|
|
262
|
+
|
|
263
|
+
### Arguments
|
|
264
|
+
|
|
265
|
+
| Name | Required | Variadic | Description |
|
|
266
|
+
| ------ | -------- | -------- | ----------- |
|
|
267
|
+
| `path` | no | no | |
|
|
262
268
|
|
|
263
269
|
### Options
|
|
264
270
|
|
|
@@ -191,6 +191,76 @@ The script runs in a WASM sandbox: 5s wall clock, 10 MB heap, no network,
|
|
|
191
191
|
no filesystem. Trips show up as `script_timeout` / `script_memory_limit`
|
|
192
192
|
on the step execution.
|
|
193
193
|
|
|
194
|
+
## Be specific with types
|
|
195
|
+
|
|
196
|
+
Every output schema you write is read three times: by the LLM (for
|
|
197
|
+
`ai.extract`), by downstream steps (autocomplete + template resolution),
|
|
198
|
+
and by the runtime validator. **The more specific the type, the better
|
|
199
|
+
the workflow runs.**
|
|
200
|
+
|
|
201
|
+
### Use `enum` for coded values
|
|
202
|
+
|
|
203
|
+
Categorical fields like `category`, `status`, `kind`, `severity`,
|
|
204
|
+
`monitoring`, `frequency` should be enums whenever the value set is
|
|
205
|
+
closed. The LLM is constrained to emit only allowed values, and
|
|
206
|
+
downstream `transform.script` steps know exactly what they'll see.
|
|
207
|
+
|
|
208
|
+
```yaml
|
|
209
|
+
- name: classify
|
|
210
|
+
type: ai.extract
|
|
211
|
+
with:
|
|
212
|
+
input: '{{ input.text }}'
|
|
213
|
+
schema:
|
|
214
|
+
type: object
|
|
215
|
+
properties:
|
|
216
|
+
category:
|
|
217
|
+
type: string
|
|
218
|
+
enum: ['901', '902']
|
|
219
|
+
description: |
|
|
220
|
+
Covenant category.
|
|
221
|
+
901 = Odkladacie podmienky (conditions precedent).
|
|
222
|
+
902 = Následné podmienky (subsequent / ongoing covenants).
|
|
223
|
+
required: [category]
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Per-value meanings go inline in the field's `description`. The LLM picks
|
|
227
|
+
them up reliably from there, and the scope browser renders the allowed
|
|
228
|
+
values when downstream steps reference the field.
|
|
229
|
+
|
|
230
|
+
### `transform.script` — TS literal unions become JSON Schema enums
|
|
231
|
+
|
|
232
|
+
A TS literal-union return type compiles to a JSON-Schema enum
|
|
233
|
+
automatically. Use it when the function returns a value from a closed set:
|
|
234
|
+
|
|
235
|
+
```yaml
|
|
236
|
+
- name: severity
|
|
237
|
+
type: transform.script
|
|
238
|
+
with:
|
|
239
|
+
inputs:
|
|
240
|
+
score: '{{ steps.score.output.value }}'
|
|
241
|
+
function: |
|
|
242
|
+
function script(score: number): { level: 'low' | 'medium' | 'high' } {
|
|
243
|
+
return { level: score > 0.8 ? 'high' : score > 0.4 ? 'medium' : 'low' };
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Downstream steps autocomplete `low | medium | high` for `level`.
|
|
248
|
+
|
|
249
|
+
### `eigenpal workflow push` warns on weak types
|
|
250
|
+
|
|
251
|
+
Push surfaces non-fatal **schema-quality warnings** when it sees patterns
|
|
252
|
+
that hurt downstream typing. Common ones:
|
|
253
|
+
|
|
254
|
+
| Warning code | What it means |
|
|
255
|
+
| ---------------------------- | -------------------------------------------------------------------------------------------- |
|
|
256
|
+
| `categorical-missing-enum` | A field named like `category` / `status` / `kind` is a plain `string`. Consider declaring an `enum`. |
|
|
257
|
+
| `weak-script-return` | A `transform.script` returns `any` or `unknown`. Replace with a concrete shape or a literal union. |
|
|
258
|
+
| `untyped-object` | A `type: object` field has no `properties`. The LLM has no guidance on what to emit. |
|
|
259
|
+
| `untyped-array-items` | A `type: array` field has no `items:` declared. |
|
|
260
|
+
|
|
261
|
+
Warnings do not block the push. They print to stderr as `! …` lines. Run
|
|
262
|
+
`eigenpal workflow validate` locally to surface them before pushing.
|
|
263
|
+
|
|
194
264
|
## Common patterns
|
|
195
265
|
|
|
196
266
|
### Conditional branches
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eigenpal/cli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.9",
|
|
4
4
|
"description": "Eigenpal CLI — author and run document workflows from your terminal. Agent-ready.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eigenpal",
|
|
@@ -40,6 +40,8 @@
|
|
|
40
40
|
"scripts": {
|
|
41
41
|
"dev": "bun src/cli.ts",
|
|
42
42
|
"build": "bun run generate:skill && bun build src/cli.ts --outdir dist --target node --format esm && rm -rf dist/templates dist/skill && cp -R src/templates dist/templates && cp -R src/skill dist/skill",
|
|
43
|
+
"generate": "bun run generate:cli-docs && bun run generate:skill",
|
|
44
|
+
"generate:check": "bun run generate:cli-docs:check && bun run generate:skill:check",
|
|
43
45
|
"generate:skill": "bun scripts/generate-skill-reference.ts",
|
|
44
46
|
"generate:skill:check": "bun scripts/generate-skill-reference.ts --check",
|
|
45
47
|
"generate:cli-docs": "bun scripts/generate-cli-docs.ts",
|