yaml-flow 2.2.0 → 2.3.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.
- package/README.md +40 -0
- package/dist/{constants-Bwvkbr5s.d.cts → constants-BftTHiuV.d.cts} +51 -1
- package/dist/{constants-Ewufm5cK.d.ts → constants-DMbnp--H.d.ts} +51 -1
- package/dist/event-graph/index.cjs +208 -0
- package/dist/event-graph/index.cjs.map +1 -1
- package/dist/event-graph/index.d.cts +2 -2
- package/dist/event-graph/index.d.ts +2 -2
- package/dist/event-graph/index.js +208 -1
- package/dist/event-graph/index.js.map +1 -1
- package/dist/index.cjs +208 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +208 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -801,6 +801,44 @@ Entry points (no requires) get rounded shapes, leaf tasks get double-bracketed s
|
|
|
801
801
|
|
|
802
802
|
---
|
|
803
803
|
|
|
804
|
+
## Graph Validation (Semantic)
|
|
805
|
+
|
|
806
|
+
Validate the logical correctness of a graph — catches issues that structural validation (`validateGraphConfig`) can't.
|
|
807
|
+
|
|
808
|
+
```typescript
|
|
809
|
+
import { validateGraph } from 'yaml-flow/event-graph';
|
|
810
|
+
|
|
811
|
+
const result = validateGraph(graph);
|
|
812
|
+
|
|
813
|
+
result.valid; // true if no errors (warnings/info allowed)
|
|
814
|
+
result.errors; // issues that will break execution
|
|
815
|
+
result.warnings; // issues that may cause unexpected behavior
|
|
816
|
+
result.issues; // all issues (errors + warnings + info)
|
|
817
|
+
|
|
818
|
+
// Each issue
|
|
819
|
+
result.issues[0].severity; // 'error' | 'warning' | 'info'
|
|
820
|
+
result.issues[0].code; // e.g. 'CIRCULAR_DEPENDENCY'
|
|
821
|
+
result.issues[0].message; // human-readable description
|
|
822
|
+
result.issues[0].tasks; // affected task names
|
|
823
|
+
result.issues[0].tokens; // affected tokens
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
| Issue Code | Severity | Description |
|
|
827
|
+
|---|---|---|
|
|
828
|
+
| `EMPTY_GRAPH` | error | Graph has no tasks |
|
|
829
|
+
| `DANGLING_REQUIRES` | error | Task requires a token that no task produces |
|
|
830
|
+
| `CIRCULAR_DEPENDENCY` | error | Cycle detected in task dependencies |
|
|
831
|
+
| `SELF_DEPENDENCY` | error | Task requires a token it provides itself |
|
|
832
|
+
| `UNREACHABLE_GOAL` | error | Goal token cannot be produced by any task |
|
|
833
|
+
| `MISSING_GOAL` | error | `goal-reached` strategy without goal array |
|
|
834
|
+
| `PROVIDE_CONFLICT` | warning | Multiple tasks produce the same token |
|
|
835
|
+
| `DEAD_END_TASK` | warning | Task has no provides — can't unblock downstream |
|
|
836
|
+
| `ISOLATED_TASK` | info | Disconnected task with no requires or dependents |
|
|
837
|
+
|
|
838
|
+
Use `validateGraphConfig()` for structural checks (JSON shape) and `validateGraph()` for semantic checks (logical correctness). Both are pure functions.
|
|
839
|
+
|
|
840
|
+
---
|
|
841
|
+
|
|
804
842
|
## Loading & Exporting Graph Configs
|
|
805
843
|
|
|
806
844
|
```typescript
|
|
@@ -835,6 +873,7 @@ import { next, apply, applyAll, getCandidateTasks } from 'yaml-flow/event-graph'
|
|
|
835
873
|
import { createInitialExecutionState, isExecutionComplete, detectStuckState } from 'yaml-flow/event-graph';
|
|
836
874
|
import { planExecution, graphToMermaid, flowToMermaid } from 'yaml-flow/event-graph';
|
|
837
875
|
import { loadGraphConfig, validateGraphConfig, exportGraphConfig } from 'yaml-flow/event-graph';
|
|
876
|
+
import { validateGraph } from 'yaml-flow/event-graph';
|
|
838
877
|
import { TASK_STATUS, COMPLETION_STRATEGIES, CONFLICT_STRATEGIES } from 'yaml-flow/event-graph';
|
|
839
878
|
|
|
840
879
|
// Stores
|
|
@@ -889,6 +928,7 @@ import { FlowEngine, createEngine } from 'yaml-flow'; // aliases for StepMachin
|
|
|
889
928
|
| `validateGraphConfig(config)` | Validate a GraphConfig, returns error strings |
|
|
890
929
|
| `exportGraphConfig(config, options?)` | Export a GraphConfig to JSON or YAML string |
|
|
891
930
|
| `exportGraphConfigToFile(config, path)` | Export a GraphConfig to a file |
|
|
931
|
+
| `validateGraph(graph)` | Semantic validation: cycles, dangling requires, unreachable goals, conflicts |
|
|
892
932
|
|
|
893
933
|
### Event Types (for `apply()`)
|
|
894
934
|
|
|
@@ -438,6 +438,56 @@ declare function exportGraphConfig(config: GraphConfig, options?: ExportOptions)
|
|
|
438
438
|
*/
|
|
439
439
|
declare function exportGraphConfigToFile(config: GraphConfig, filePath: string, options?: ExportOptions): Promise<void>;
|
|
440
440
|
|
|
441
|
+
/**
|
|
442
|
+
* Event Graph — Semantic Graph Validation
|
|
443
|
+
*
|
|
444
|
+
* Validates the logical correctness of a static graph configuration.
|
|
445
|
+
* Unlike validateGraphConfig() which checks JSON structure, this checks:
|
|
446
|
+
* - Dangling requires (tokens no task produces)
|
|
447
|
+
* - Circular dependencies
|
|
448
|
+
* - Provide conflicts (multiple tasks producing same token)
|
|
449
|
+
* - Unreachable goal tokens
|
|
450
|
+
* - Dead-end tasks (no provides)
|
|
451
|
+
* - Self-dependencies
|
|
452
|
+
* - Orphaned tasks (disconnected from the graph)
|
|
453
|
+
*
|
|
454
|
+
* Pure function — config in, diagnostics out.
|
|
455
|
+
*/
|
|
456
|
+
|
|
457
|
+
type IssueSeverity = 'error' | 'warning' | 'info';
|
|
458
|
+
interface GraphIssue {
|
|
459
|
+
/** Severity: error = will break execution, warning = may cause problems, info = notable */
|
|
460
|
+
severity: IssueSeverity;
|
|
461
|
+
/** Machine-readable issue code */
|
|
462
|
+
code: string;
|
|
463
|
+
/** Human-readable description */
|
|
464
|
+
message: string;
|
|
465
|
+
/** Affected task names (if applicable) */
|
|
466
|
+
tasks?: string[];
|
|
467
|
+
/** Affected tokens (if applicable) */
|
|
468
|
+
tokens?: string[];
|
|
469
|
+
}
|
|
470
|
+
interface GraphValidationResult {
|
|
471
|
+
/** true if no errors (warnings/info are allowed) */
|
|
472
|
+
valid: boolean;
|
|
473
|
+
/** All issues found */
|
|
474
|
+
issues: GraphIssue[];
|
|
475
|
+
/** Just the errors */
|
|
476
|
+
errors: GraphIssue[];
|
|
477
|
+
/** Just the warnings */
|
|
478
|
+
warnings: GraphIssue[];
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Validate the semantic correctness of a static event-graph configuration.
|
|
482
|
+
*
|
|
483
|
+
* Checks for logical issues that would cause execution failures, stuck states,
|
|
484
|
+
* or unexpected behavior. Does NOT check JSON structure (use validateGraphConfig for that).
|
|
485
|
+
*
|
|
486
|
+
* @param graph - The event-graph configuration to validate
|
|
487
|
+
* @returns Validation result with categorized issues
|
|
488
|
+
*/
|
|
489
|
+
declare function validateGraph(graph: GraphConfig): GraphValidationResult;
|
|
490
|
+
|
|
441
491
|
/**
|
|
442
492
|
* Event Graph — Constants
|
|
443
493
|
*/
|
|
@@ -454,4 +504,4 @@ declare const DEFAULTS: {
|
|
|
454
504
|
readonly MAX_ITERATIONS: 1000;
|
|
455
505
|
};
|
|
456
506
|
|
|
457
|
-
export {
|
|
507
|
+
export { isTaskCompleted as $, type AgentActionEvent as A, applyAll as B, COMPLETION_STRATEGIES as C, DEFAULTS as D, EXECUTION_MODES as E, computeAvailableOutputs as F, type GraphConfig as G, createDefaultTaskState as H, type InjectTokensEvent as I, createInitialExecutionState as J, detectStuckState as K, exportGraphConfig as L, type MermaidOptions as M, exportGraphConfigToFile as N, flowToMermaid as O, getAllTasks as P, getCandidateTasks as Q, getProvides as R, type SchedulerResult as S, type TaskConfig as T, getRequires as U, getTask as V, graphToMermaid as W, hasTask as X, isExecutionComplete as Y, isNonActiveTask as Z, isRepeatableTask as _, CONFLICT_STRATEGIES as a, isTaskRunning as a0, loadGraphConfig as a1, next as a2, planExecution as a3, validateGraph as a4, validateGraphConfig as a5, type RepeatableConfig as a6, type TaskCircuitBreakerConfig as a7, type TaskMessage as a8, type TaskProgressEvent as a9, type TaskRetryConfig as aa, addKeyToProvides as ab, addKeyToRequires as ac, getRepeatableMax as ad, groupTasksByProvides as ae, hasOutputConflict as af, removeKeyFromProvides as ag, removeKeyFromRequires as ah, type CompletionResult as b, type CompletionStrategy as c, type ConflictStrategy as d, EXECUTION_STATUS as e, type ExecutionConfig as f, type ExecutionMode as g, type ExecutionPlan as h, type ExecutionState as i, type ExecutionStatus as j, type ExportOptions as k, type GraphEvent as l, type GraphIssue as m, type GraphSettings as n, type GraphValidationResult as o, type IssueSeverity as p, type StuckDetection as q, TASK_STATUS as r, type TaskCompletedEvent as s, type TaskCreationEvent as t, type TaskFailedEvent as u, type TaskStartedEvent as v, type TaskState as w, type TaskStatus as x, addDynamicTask as y, apply as z };
|
|
@@ -438,6 +438,56 @@ declare function exportGraphConfig(config: GraphConfig, options?: ExportOptions)
|
|
|
438
438
|
*/
|
|
439
439
|
declare function exportGraphConfigToFile(config: GraphConfig, filePath: string, options?: ExportOptions): Promise<void>;
|
|
440
440
|
|
|
441
|
+
/**
|
|
442
|
+
* Event Graph — Semantic Graph Validation
|
|
443
|
+
*
|
|
444
|
+
* Validates the logical correctness of a static graph configuration.
|
|
445
|
+
* Unlike validateGraphConfig() which checks JSON structure, this checks:
|
|
446
|
+
* - Dangling requires (tokens no task produces)
|
|
447
|
+
* - Circular dependencies
|
|
448
|
+
* - Provide conflicts (multiple tasks producing same token)
|
|
449
|
+
* - Unreachable goal tokens
|
|
450
|
+
* - Dead-end tasks (no provides)
|
|
451
|
+
* - Self-dependencies
|
|
452
|
+
* - Orphaned tasks (disconnected from the graph)
|
|
453
|
+
*
|
|
454
|
+
* Pure function — config in, diagnostics out.
|
|
455
|
+
*/
|
|
456
|
+
|
|
457
|
+
type IssueSeverity = 'error' | 'warning' | 'info';
|
|
458
|
+
interface GraphIssue {
|
|
459
|
+
/** Severity: error = will break execution, warning = may cause problems, info = notable */
|
|
460
|
+
severity: IssueSeverity;
|
|
461
|
+
/** Machine-readable issue code */
|
|
462
|
+
code: string;
|
|
463
|
+
/** Human-readable description */
|
|
464
|
+
message: string;
|
|
465
|
+
/** Affected task names (if applicable) */
|
|
466
|
+
tasks?: string[];
|
|
467
|
+
/** Affected tokens (if applicable) */
|
|
468
|
+
tokens?: string[];
|
|
469
|
+
}
|
|
470
|
+
interface GraphValidationResult {
|
|
471
|
+
/** true if no errors (warnings/info are allowed) */
|
|
472
|
+
valid: boolean;
|
|
473
|
+
/** All issues found */
|
|
474
|
+
issues: GraphIssue[];
|
|
475
|
+
/** Just the errors */
|
|
476
|
+
errors: GraphIssue[];
|
|
477
|
+
/** Just the warnings */
|
|
478
|
+
warnings: GraphIssue[];
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Validate the semantic correctness of a static event-graph configuration.
|
|
482
|
+
*
|
|
483
|
+
* Checks for logical issues that would cause execution failures, stuck states,
|
|
484
|
+
* or unexpected behavior. Does NOT check JSON structure (use validateGraphConfig for that).
|
|
485
|
+
*
|
|
486
|
+
* @param graph - The event-graph configuration to validate
|
|
487
|
+
* @returns Validation result with categorized issues
|
|
488
|
+
*/
|
|
489
|
+
declare function validateGraph(graph: GraphConfig): GraphValidationResult;
|
|
490
|
+
|
|
441
491
|
/**
|
|
442
492
|
* Event Graph — Constants
|
|
443
493
|
*/
|
|
@@ -454,4 +504,4 @@ declare const DEFAULTS: {
|
|
|
454
504
|
readonly MAX_ITERATIONS: 1000;
|
|
455
505
|
};
|
|
456
506
|
|
|
457
|
-
export {
|
|
507
|
+
export { isTaskCompleted as $, type AgentActionEvent as A, applyAll as B, COMPLETION_STRATEGIES as C, DEFAULTS as D, EXECUTION_MODES as E, computeAvailableOutputs as F, type GraphConfig as G, createDefaultTaskState as H, type InjectTokensEvent as I, createInitialExecutionState as J, detectStuckState as K, exportGraphConfig as L, type MermaidOptions as M, exportGraphConfigToFile as N, flowToMermaid as O, getAllTasks as P, getCandidateTasks as Q, getProvides as R, type SchedulerResult as S, type TaskConfig as T, getRequires as U, getTask as V, graphToMermaid as W, hasTask as X, isExecutionComplete as Y, isNonActiveTask as Z, isRepeatableTask as _, CONFLICT_STRATEGIES as a, isTaskRunning as a0, loadGraphConfig as a1, next as a2, planExecution as a3, validateGraph as a4, validateGraphConfig as a5, type RepeatableConfig as a6, type TaskCircuitBreakerConfig as a7, type TaskMessage as a8, type TaskProgressEvent as a9, type TaskRetryConfig as aa, addKeyToProvides as ab, addKeyToRequires as ac, getRepeatableMax as ad, groupTasksByProvides as ae, hasOutputConflict as af, removeKeyFromProvides as ag, removeKeyFromRequires as ah, type CompletionResult as b, type CompletionStrategy as c, type ConflictStrategy as d, EXECUTION_STATUS as e, type ExecutionConfig as f, type ExecutionMode as g, type ExecutionPlan as h, type ExecutionState as i, type ExecutionStatus as j, type ExportOptions as k, type GraphEvent as l, type GraphIssue as m, type GraphSettings as n, type GraphValidationResult as o, type IssueSeverity as p, type StuckDetection as q, TASK_STATUS as r, type TaskCompletedEvent as s, type TaskCreationEvent as t, type TaskFailedEvent as u, type TaskStartedEvent as v, type TaskState as w, type TaskStatus as x, addDynamicTask as y, apply as z };
|
|
@@ -1254,6 +1254,213 @@ ${serialized}`;
|
|
|
1254
1254
|
return String(obj);
|
|
1255
1255
|
}
|
|
1256
1256
|
|
|
1257
|
+
// src/event-graph/validate.ts
|
|
1258
|
+
function buildProducerMap2(tasks) {
|
|
1259
|
+
const map = {};
|
|
1260
|
+
for (const [name, config] of Object.entries(tasks)) {
|
|
1261
|
+
for (const token of getProvides(config)) {
|
|
1262
|
+
if (!map[token]) map[token] = [];
|
|
1263
|
+
map[token].push(name);
|
|
1264
|
+
}
|
|
1265
|
+
if (config.on) {
|
|
1266
|
+
for (const tokens of Object.values(config.on)) {
|
|
1267
|
+
for (const token of tokens) {
|
|
1268
|
+
if (!map[token]) map[token] = [];
|
|
1269
|
+
if (!map[token].includes(name)) map[token].push(name);
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
if (config.on_failure) {
|
|
1274
|
+
for (const token of config.on_failure) {
|
|
1275
|
+
if (!map[token]) map[token] = [];
|
|
1276
|
+
if (!map[token].includes(name)) map[token].push(name);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
return map;
|
|
1281
|
+
}
|
|
1282
|
+
function buildTaskDeps(tasks, producerMap) {
|
|
1283
|
+
const deps = {};
|
|
1284
|
+
for (const [name, config] of Object.entries(tasks)) {
|
|
1285
|
+
deps[name] = /* @__PURE__ */ new Set();
|
|
1286
|
+
for (const token of getRequires(config)) {
|
|
1287
|
+
for (const producer of producerMap[token] || []) {
|
|
1288
|
+
if (producer !== name) deps[name].add(producer);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
return deps;
|
|
1293
|
+
}
|
|
1294
|
+
function detectCycles(taskNames, deps) {
|
|
1295
|
+
const WHITE = 0, GRAY = 1, BLACK = 2;
|
|
1296
|
+
const color = {};
|
|
1297
|
+
const parent = {};
|
|
1298
|
+
const cycles = [];
|
|
1299
|
+
for (const name of taskNames) {
|
|
1300
|
+
color[name] = WHITE;
|
|
1301
|
+
parent[name] = null;
|
|
1302
|
+
}
|
|
1303
|
+
function dfs(node) {
|
|
1304
|
+
color[node] = GRAY;
|
|
1305
|
+
for (const dep of deps[node] || []) {
|
|
1306
|
+
if (color[dep] === GRAY) {
|
|
1307
|
+
const cycle = [dep];
|
|
1308
|
+
let cur = node;
|
|
1309
|
+
while (cur !== dep) {
|
|
1310
|
+
cycle.push(cur);
|
|
1311
|
+
cur = parent[cur];
|
|
1312
|
+
}
|
|
1313
|
+
cycle.push(dep);
|
|
1314
|
+
cycle.reverse();
|
|
1315
|
+
cycles.push(cycle);
|
|
1316
|
+
} else if (color[dep] === WHITE) {
|
|
1317
|
+
parent[dep] = node;
|
|
1318
|
+
dfs(dep);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
color[node] = BLACK;
|
|
1322
|
+
}
|
|
1323
|
+
for (const name of taskNames) {
|
|
1324
|
+
if (color[name] === WHITE) {
|
|
1325
|
+
dfs(name);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
return cycles;
|
|
1329
|
+
}
|
|
1330
|
+
function validateGraph(graph) {
|
|
1331
|
+
const issues = [];
|
|
1332
|
+
const tasks = getAllTasks(graph);
|
|
1333
|
+
const taskNames = Object.keys(tasks);
|
|
1334
|
+
if (taskNames.length === 0) {
|
|
1335
|
+
issues.push({
|
|
1336
|
+
severity: "error",
|
|
1337
|
+
code: "EMPTY_GRAPH",
|
|
1338
|
+
message: "Graph has no tasks"
|
|
1339
|
+
});
|
|
1340
|
+
return buildResult(issues);
|
|
1341
|
+
}
|
|
1342
|
+
const producerMap = buildProducerMap2(tasks);
|
|
1343
|
+
const deps = buildTaskDeps(tasks, producerMap);
|
|
1344
|
+
for (const [name, config] of Object.entries(tasks)) {
|
|
1345
|
+
for (const token of getRequires(config)) {
|
|
1346
|
+
if (!producerMap[token]) {
|
|
1347
|
+
issues.push({
|
|
1348
|
+
severity: "error",
|
|
1349
|
+
code: "DANGLING_REQUIRES",
|
|
1350
|
+
message: `Task "${name}" requires token "${token}" but no task produces it`,
|
|
1351
|
+
tasks: [name],
|
|
1352
|
+
tokens: [token]
|
|
1353
|
+
});
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
const cycles = detectCycles(taskNames, deps);
|
|
1358
|
+
for (const cycle of cycles) {
|
|
1359
|
+
issues.push({
|
|
1360
|
+
severity: "error",
|
|
1361
|
+
code: "CIRCULAR_DEPENDENCY",
|
|
1362
|
+
message: `Circular dependency: ${cycle.join(" \u2192 ")}`,
|
|
1363
|
+
tasks: cycle.filter((_t, i) => i < cycle.length - 1)
|
|
1364
|
+
// dedupe last = first
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
for (const [name, config] of Object.entries(tasks)) {
|
|
1368
|
+
const req = getRequires(config);
|
|
1369
|
+
const prov = getProvides(config);
|
|
1370
|
+
const self = req.filter((token) => prov.includes(token));
|
|
1371
|
+
if (self.length > 0) {
|
|
1372
|
+
issues.push({
|
|
1373
|
+
severity: "error",
|
|
1374
|
+
code: "SELF_DEPENDENCY",
|
|
1375
|
+
message: `Task "${name}" requires tokens it provides itself: [${self.join(", ")}]`,
|
|
1376
|
+
tasks: [name],
|
|
1377
|
+
tokens: self
|
|
1378
|
+
});
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
for (const [token, producers] of Object.entries(producerMap)) {
|
|
1382
|
+
if (producers.length > 1) {
|
|
1383
|
+
issues.push({
|
|
1384
|
+
severity: "warning",
|
|
1385
|
+
code: "PROVIDE_CONFLICT",
|
|
1386
|
+
message: `Token "${token}" is produced by multiple tasks: [${producers.join(", ")}]. This requires a conflict strategy.`,
|
|
1387
|
+
tasks: producers,
|
|
1388
|
+
tokens: [token]
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
if (graph.settings.completion === "goal-reached" && graph.settings.goal) {
|
|
1393
|
+
for (const goalToken of graph.settings.goal) {
|
|
1394
|
+
if (!producerMap[goalToken]) {
|
|
1395
|
+
issues.push({
|
|
1396
|
+
severity: "error",
|
|
1397
|
+
code: "UNREACHABLE_GOAL",
|
|
1398
|
+
message: `Goal token "${goalToken}" cannot be produced by any task`,
|
|
1399
|
+
tokens: [goalToken]
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
if (taskNames.length > 1) {
|
|
1405
|
+
for (const [name, config] of Object.entries(tasks)) {
|
|
1406
|
+
const prov = getProvides(config);
|
|
1407
|
+
const onProv = config.on ? Object.values(config.on).flat() : [];
|
|
1408
|
+
const failProv = config.on_failure || [];
|
|
1409
|
+
if (prov.length === 0 && onProv.length === 0 && failProv.length === 0) {
|
|
1410
|
+
issues.push({
|
|
1411
|
+
severity: "warning",
|
|
1412
|
+
code: "DEAD_END_TASK",
|
|
1413
|
+
message: `Task "${name}" has no provides \u2014 it cannot unblock any downstream task`,
|
|
1414
|
+
tasks: [name]
|
|
1415
|
+
});
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
const allRequired = /* @__PURE__ */ new Set();
|
|
1420
|
+
for (const config of Object.values(tasks)) {
|
|
1421
|
+
for (const token of getRequires(config)) {
|
|
1422
|
+
allRequired.add(token);
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
for (const [name, config] of Object.entries(tasks)) {
|
|
1426
|
+
const req = getRequires(config);
|
|
1427
|
+
const prov = getProvides(config);
|
|
1428
|
+
const onProv = config.on ? Object.values(config.on).flat() : [];
|
|
1429
|
+
const allProv = [...prov, ...onProv];
|
|
1430
|
+
const isEntryPoint = req.length === 0;
|
|
1431
|
+
const hasDownstream = allProv.some((token) => allRequired.has(token));
|
|
1432
|
+
if (isEntryPoint && !hasDownstream && taskNames.length > 1) {
|
|
1433
|
+
const isGoalRelevant = graph.settings.completion === "goal-reached" && graph.settings.goal?.some((g) => allProv.includes(g));
|
|
1434
|
+
if (!isGoalRelevant) {
|
|
1435
|
+
issues.push({
|
|
1436
|
+
severity: "info",
|
|
1437
|
+
code: "ISOLATED_TASK",
|
|
1438
|
+
message: `Task "${name}" is disconnected \u2014 it has no requires and nothing depends on its provides`,
|
|
1439
|
+
tasks: [name]
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
if (graph.settings.completion === "goal-reached" && !graph.settings.goal) {
|
|
1445
|
+
issues.push({
|
|
1446
|
+
severity: "error",
|
|
1447
|
+
code: "MISSING_GOAL",
|
|
1448
|
+
message: 'Completion strategy is "goal-reached" but no goal tokens are defined'
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
return buildResult(issues);
|
|
1452
|
+
}
|
|
1453
|
+
function buildResult(issues) {
|
|
1454
|
+
const errors = issues.filter((i) => i.severity === "error");
|
|
1455
|
+
const warnings = issues.filter((i) => i.severity === "warning");
|
|
1456
|
+
return {
|
|
1457
|
+
valid: errors.length === 0,
|
|
1458
|
+
issues,
|
|
1459
|
+
errors,
|
|
1460
|
+
warnings
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1257
1464
|
exports.COMPLETION_STRATEGIES = COMPLETION_STRATEGIES;
|
|
1258
1465
|
exports.CONFLICT_STRATEGIES = CONFLICT_STRATEGIES;
|
|
1259
1466
|
exports.DEFAULTS = DEFAULTS;
|
|
@@ -1299,6 +1506,7 @@ exports.removeKeyFromProvides = removeKeyFromProvides;
|
|
|
1299
1506
|
exports.removeKeyFromRequires = removeKeyFromRequires;
|
|
1300
1507
|
exports.selectBestAlternative = selectBestAlternative;
|
|
1301
1508
|
exports.selectRandomTasks = selectRandomTasks;
|
|
1509
|
+
exports.validateGraph = validateGraph;
|
|
1302
1510
|
exports.validateGraphConfig = validateGraphConfig;
|
|
1303
1511
|
//# sourceMappingURL=index.cjs.map
|
|
1304
1512
|
//# sourceMappingURL=index.cjs.map
|