@worca/app 1.0.0 → 1.1.1
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 +22 -9
- package/agents/clarify.meta.json +4 -4
- package/agents/decomposer.meta.json +5 -5
- package/agents/implementer.meta.json +15 -5
- package/agents/manualTestsChecklist.meta.json +5 -4
- package/agents/manualWebUiTesting.meta.json +9 -4
- package/agents/planReviewer.meta.json +12 -4
- package/agents/planner.meta.json +12 -5
- package/agents/refiner.meta.json +15 -4
- package/agents/reviewer.meta.json +14 -4
- package/agents/worca-cc-clarify.md +7 -0
- package/agents/worca-cc-code-reviewer.md +11 -6
- package/agents/worca-cc-decomposer.md +7 -0
- package/agents/worca-cc-implementer.md +9 -0
- package/agents/worca-cc-manual-tests-checklist.md +8 -5
- package/agents/worca-cc-manual-web-ui-testing.md +10 -6
- package/agents/worca-cc-plan-refiner.md +11 -6
- package/agents/worca-cc-plan-reviewer.md +10 -7
- package/agents/worca-cc-planner.md +9 -0
- package/agents/worca-cc-workspace-reviewer.md +11 -4
- package/agents/worca-cc-workspace-scanner.md +8 -4
- package/agents/workspaceReviewer.meta.json +15 -4
- package/agents/workspaceScanner.meta.json +5 -4
- package/package.json +8 -2
- package/skills/worca/SKILL.md +5 -5
- package/src/cli/render.mjs +148 -0
- package/src/cli/worca-cc.mjs +319 -45
- package/src/core/agent-gen.mjs +69 -31
- package/src/core/agent-registry.mjs +124 -144
- package/src/core/agent-store.mjs +164 -4
- package/src/core/artifacts.mjs +189 -21
- package/src/core/ask/catalog.mjs +111 -0
- package/src/core/ask/comment-deps.mjs +55 -0
- package/src/core/ask/events.mjs +506 -0
- package/src/core/ask/follow.mjs +107 -0
- package/src/core/ask/git-allowlist.mjs +226 -0
- package/src/core/ask/limits.mjs +54 -0
- package/src/core/ask/mcp-stdio.mjs +135 -0
- package/src/core/ask/models.mjs +125 -0
- package/src/core/ask/prompt.mjs +261 -0
- package/src/core/ask/proposal.mjs +170 -0
- package/src/core/ask/redact.mjs +30 -0
- package/src/core/ask/spawn.mjs +153 -0
- package/src/core/ask/store.mjs +360 -0
- package/src/core/ask/tool-deps.mjs +63 -0
- package/src/core/ask/tools.mjs +848 -0
- package/src/core/ask/turn.mjs +416 -0
- package/src/core/ask/worktree-deps.mjs +27 -0
- package/src/core/ask/worktrees.mjs +285 -0
- package/src/core/chat/command-router.mjs +20 -3
- package/src/core/claude-runner.mjs +434 -57
- package/src/core/config.mjs +264 -41
- package/src/core/cost-budget.mjs +29 -2
- package/src/core/db.mjs +684 -47
- package/src/core/diff-anchor.mjs +213 -0
- package/src/core/diff-comments.mjs +273 -0
- package/src/core/engine-select.mjs +32 -0
- package/src/core/git-info.mjs +49 -10
- package/src/core/graph/builtin-workflows.mjs +51 -0
- package/src/core/graph/executor.mjs +894 -0
- package/src/core/graph/registry-ports.mjs +12 -0
- package/src/core/graph/scheduler.mjs +1065 -0
- package/src/core/graph/seed-templates.mjs +318 -0
- package/src/core/model-env.mjs +112 -8
- package/src/core/model-test.mjs +79 -0
- package/src/core/orchestrator.mjs +902 -4098
- package/src/core/overview-agent.mjs +15 -3
- package/src/core/phases.mjs +208 -537
- package/src/core/pipeline-delete.mjs +13 -2
- package/src/core/plugin-api.mjs +8 -3
- package/src/core/plugin-config.mjs +178 -28
- package/src/core/plugin-inventory.mjs +6 -2
- package/src/core/plugin-manifest.mjs +199 -11
- package/src/core/plugin-models.mjs +1 -0
- package/src/core/plugin-repo.mjs +16 -4
- package/src/core/plugin-shim-child.mjs +9 -3
- package/src/core/plugin-shim.mjs +77 -14
- package/src/core/plugin-store.mjs +236 -29
- package/src/core/plugin-workflows.mjs +90 -41
- package/src/core/preflight.mjs +135 -3
- package/src/core/projects.mjs +7 -5
- package/src/core/protocol.mjs +8 -35
- package/src/core/recoverable-error.mjs +1 -1
- package/src/core/run-harness.mjs +3585 -0
- package/src/core/run-manifest.mjs +5 -1
- package/src/core/settings.mjs +109 -13
- package/src/core/skills.mjs +10 -3
- package/src/core/source-bindings.mjs +175 -0
- package/src/core/sources.mjs +87 -25
- package/src/core/stats.mjs +25 -6
- package/src/core/title.mjs +51 -4
- package/src/core/workflows.mjs +358 -259
- package/src/core/workspace-scan.mjs +4 -0
- package/src/core/worktree.mjs +98 -7
- package/src/shared/graph/agent-meta.mjs +278 -0
- package/src/shared/graph/constants.mjs +105 -0
- package/src/shared/graph/geometry.mjs +157 -0
- package/src/shared/graph/layout.mjs +134 -0
- package/src/shared/graph/loops.mjs +130 -0
- package/src/shared/graph/manifest.mjs +257 -0
- package/src/shared/graph/ports.mjs +153 -0
- package/src/shared/graph/route.mjs +397 -0
- package/src/shared/graph/template.mjs +165 -0
- package/src/shared/graph/thumbnail.mjs +67 -0
- package/src/shared/graph/validate.mjs +491 -0
- package/src/shared/graph/verdict.mjs +41 -0
- package/ui/public/app.js +4008 -1670
- package/ui/public/ask-markdown.mjs +145 -0
- package/ui/public/ask-model.mjs +264 -0
- package/ui/public/ask-panel.mjs +1880 -0
- package/ui/public/chat-settings-view.mjs +6 -2
- package/ui/public/diff-view.mjs +66 -11
- package/ui/public/file-tree.mjs +305 -0
- package/ui/public/graph/composer.mjs +889 -0
- package/ui/public/graph/inspector.mjs +183 -0
- package/ui/public/graph/model.mjs +37 -0
- package/ui/public/graph/palette.mjs +144 -0
- package/ui/public/graph/run-decor.mjs +410 -0
- package/ui/public/graph/run-hosts.mjs +201 -0
- package/ui/public/graph/save-dialog.mjs +56 -0
- package/ui/public/graph/view.mjs +858 -0
- package/ui/public/guardrails-view.mjs +4 -2
- package/ui/public/hljs-loader.mjs +180 -0
- package/ui/public/index.html +269 -265
- package/ui/public/log-filter.mjs +22 -4
- package/ui/public/log-line.mjs +45 -19
- package/ui/public/models-view.mjs +171 -9
- package/ui/public/plugins-view.mjs +106 -4
- package/ui/public/source-pane.mjs +190 -8
- package/ui/public/stats-view.mjs +81 -1
- package/ui/public/style.css +1459 -229
- package/ui/public/syntax-highlight.mjs +270 -0
- package/ui/public/thinking-orb.mjs +110 -0
- package/ui/server.mjs +1667 -98
- package/src/core/channels.mjs +0 -302
- package/src/core/runners.mjs +0 -167
- package/src/core/workflow-validator.mjs +0 -185
- package/ui/public/composer-core.mjs +0 -211
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
// ui/public/composer-core.mjs
|
|
2
|
-
// Framework-free, DOM-free helpers for the Pipeline Composer. Imported by
|
|
3
|
-
// ui/public/app.js (browser, type="module") AND by test/composer-ui.test.mjs
|
|
4
|
-
// (node:test, no jsdom). KEEP THIS FILE FREE OF document/window references so it
|
|
5
|
-
// stays unit-testable in isolation — DOM wiring lives in app.js.
|
|
6
|
-
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
// topology(steps, feedbacks) -> WorkflowTemplate {steps,feedbacks} body.
|
|
9
|
-
// Canvas model uses throwaway local ids (n1, n7…). The persisted contract uses
|
|
10
|
-
// stable instance ids "s{stepIndex}_{memberIndex}" (e.g. "s0_0"); feedback
|
|
11
|
-
// from/to reference those instance ids. We rebuild the id map and remap edges,
|
|
12
|
-
// dropping any edge whose endpoint is gone (defensive; the UI prunes these too).
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
export function topology(steps, feedbacks) {
|
|
15
|
-
const idMap = {}; // localId -> "sI_J"
|
|
16
|
-
const outSteps = steps.map((col, i) =>
|
|
17
|
-
col.map((node, j) => {
|
|
18
|
-
const id = `s${i}_${j}`;
|
|
19
|
-
idMap[node.id] = id;
|
|
20
|
-
return { id, key: node.key };
|
|
21
|
-
}),
|
|
22
|
-
);
|
|
23
|
-
const outFeedbacks = [];
|
|
24
|
-
(feedbacks || []).forEach((fb) => {
|
|
25
|
-
const from = idMap[fb.from];
|
|
26
|
-
const to = idMap[fb.to];
|
|
27
|
-
if (from && to) outFeedbacks.push({ id: `fb_${outFeedbacks.length}`, from, to });
|
|
28
|
-
});
|
|
29
|
-
return { steps: outSteps, feedbacks: outFeedbacks };
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// metaLine(steps, feedbacks) -> "N steps · M agents[ · K feedback loop(s)]"
|
|
33
|
-
// (saved-pipelines card meta line). Mirrors the mockup's renderList meta string.
|
|
34
|
-
export function metaLine(steps, feedbacks) {
|
|
35
|
-
const nSteps = steps.length;
|
|
36
|
-
const nAgents = steps.reduce((sum, col) => sum + col.length, 0);
|
|
37
|
-
const nLoops = (feedbacks || []).length;
|
|
38
|
-
let s = `${nSteps} steps · ${nAgents} agents`;
|
|
39
|
-
if (nLoops) s += ` · ${nLoops} feedback loop${nLoops > 1 ? 's' : ''}`;
|
|
40
|
-
return s;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// distinctAgents(steps) -> ordered unique role keys (for the chip row).
|
|
44
|
-
export function distinctAgents(steps) {
|
|
45
|
-
const seen = [];
|
|
46
|
-
steps.forEach((col) => col.forEach((node) => {
|
|
47
|
-
if (!seen.includes(node.key)) seen.push(node.key);
|
|
48
|
-
}));
|
|
49
|
-
return seen;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Embedded agent registry — fallback for the palette when /api/agents is
|
|
53
|
-
// unavailable (e.g. a sibling phase's endpoint not yet wired). Keys are the
|
|
54
|
-
// canonical camelCase agent keys; icon = inner SVG markup, viewBox "0 0 24 24"
|
|
55
|
-
// (glyphs copied from the standalone mockup's ICON map). The live registry from
|
|
56
|
-
// GET /api/agents overrides this whenever present (see mergePalette).
|
|
57
|
-
export const EMBEDDED_AGENTS = {
|
|
58
|
-
clarify: {
|
|
59
|
-
key: 'clarify', displayName: 'Clarify', description: 'Turns hidden decisions into questions before planning. Multiple-choice, so later steps never guess.',
|
|
60
|
-
color: 'red', order: 0, connectsTo: ['planner'],
|
|
61
|
-
icon: '<circle cx="12" cy="12" r="9"/><path d="M9.4 9.3a2.7 2.7 0 0 1 5.2 1c0 1.8-2.6 2.1-2.6 3.6" stroke-linecap="round" fill="none"/><circle cx="12" cy="17" r="0.7" fill="currentColor" stroke="none"/>',
|
|
62
|
-
},
|
|
63
|
-
planner: {
|
|
64
|
-
key: 'planner', displayName: 'Plan', description: 'Explores the codebase and writes the implementation plan. Architecture, task breakdown, concrete code snippets; can ask clarifying questions first.',
|
|
65
|
-
color: 'violet', order: 1, connectsTo: ['refiner', 'implementer', 'decomposer'],
|
|
66
|
-
icon: '<path d="M8 6h11M8 12h11M8 18h8" stroke-linecap="round"/><circle cx="4" cy="6" r="1.1"/><circle cx="4" cy="12" r="1.1"/><circle cx="4" cy="18" r="1.1"/>',
|
|
67
|
-
},
|
|
68
|
-
refiner: {
|
|
69
|
-
key: 'refiner', displayName: 'Refine Plan', description: 'Rewrites the latest plan into a tighter version. Fixes structure, correctness, and code snippets until no blocking issues remain.',
|
|
70
|
-
color: 'green', order: 2, connectsTo: ['implementer', 'refiner', 'decomposer'],
|
|
71
|
-
icon: '<path d="M12 3v3M12 18v3M4.5 7.5l2 1M17.5 15.5l2 1M4.5 16.5l2-1M17.5 8.5l2-1" stroke-linecap="round"/><path d="M12 8.2l1.2 2.6L16 12l-2.8 1.2L12 15.8l-1.2-2.6L8 12l2.8-1.2L12 8.2Z" stroke-linejoin="round"/>',
|
|
72
|
-
},
|
|
73
|
-
decomposer: {
|
|
74
|
-
key: 'decomposer', displayName: 'Decompose', description: 'Splits an approved plan into vertical-slice tasks. Each task gets its own implementer.',
|
|
75
|
-
color: 'blue', order: 2.5, connectsTo: ['implementer'],
|
|
76
|
-
},
|
|
77
|
-
implementer: {
|
|
78
|
-
key: 'implementer', displayName: 'Implementation', description: 'Writes the code from the approved plan, strict TDD. In fix mode, addresses only the issues a review flagged.',
|
|
79
|
-
color: 'peach', order: 3, connectsTo: ['reviewer', 'manualTestsChecklist'],
|
|
80
|
-
icon: '<path d="M9 8l-4 4 4 4M15 8l4 4-4 4" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
81
|
-
},
|
|
82
|
-
reviewer: {
|
|
83
|
-
key: 'reviewer', displayName: 'Review Implementation', description: 'Reviews the implementation diff against the plan. Honest verdict; blocking findings loop back to the implementer.',
|
|
84
|
-
color: 'blue', order: 4, connectsTo: ['implementer', 'manualTestsChecklist'],
|
|
85
|
-
icon: '<path d="M12 3l7 3v5c0 4.4-3 7.6-7 9-4-1.4-7-4.6-7-9V6l7-3Z" stroke-linejoin="round"/><path d="M9 12l2 2 4-4" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
86
|
-
},
|
|
87
|
-
manualTestsChecklist: {
|
|
88
|
-
key: 'manualTestsChecklist', displayName: 'Manual Tests Checklist', description: 'Drafts a manual test checklist for the change. User-visible flows, edge cases, regressions worth clicking through.',
|
|
89
|
-
color: 'blue', order: 5, connectsTo: ['manualWebUiTesting'],
|
|
90
|
-
icon: '<rect x="6" y="4" width="12" height="17" rx="2"/><path d="M9.5 4V2.8h5V4" stroke-linejoin="round"/><path d="M8.8 12l1.6 1.6L13.4 10" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
91
|
-
},
|
|
92
|
-
manualWebUiTesting: {
|
|
93
|
-
key: 'manualWebUiTesting', displayName: 'Manual web UI testing', description: 'Runs the manual checklist in the live web UI via Playwright. Reports what passed, failed, or blocked.',
|
|
94
|
-
color: 'violet', order: 6, connectsTo: ['implementer'],
|
|
95
|
-
icon: '<circle cx="12" cy="12" r="9"/><path d="M10 8.5l5 3.5-5 3.5V8.5Z" fill="currentColor" stroke="none"/>',
|
|
96
|
-
},
|
|
97
|
-
planReviewer: {
|
|
98
|
-
key: 'planReviewer', displayName: 'Plan Review', description: 'Reviews the plan against the request and the codebase. Blocking issues bounce it back for a cold re-plan.',
|
|
99
|
-
color: 'amber', order: 7, connectsTo: ['planner', 'implementer', 'decomposer'],
|
|
100
|
-
icon: '<path d="M10.5 4a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13Z"/><path d="M15.5 15.5L21 21" stroke-linecap="round"/><path d="M7.6 10.3l2 2 3.3-3.6" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
101
|
-
},
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
// mergePalette(agentsResponse) -> ordered Array<{key,displayName,description,color,icon,origin,order}>.
|
|
105
|
-
// Prefers the live registry (GET /api/agents -> { agents:[…] } or a bare array);
|
|
106
|
-
// falls back to EMBEDDED_AGENTS. Always sorted by .order so the palette is stable.
|
|
107
|
-
export function mergePalette(agentsResponse) {
|
|
108
|
-
let list = null;
|
|
109
|
-
if (Array.isArray(agentsResponse)) list = agentsResponse;
|
|
110
|
-
else if (agentsResponse && Array.isArray(agentsResponse.agents)) list = agentsResponse.agents;
|
|
111
|
-
if (!list || !list.length) list = Object.values(EMBEDDED_AGENTS);
|
|
112
|
-
return list
|
|
113
|
-
.map((a) => ({
|
|
114
|
-
key: a.key,
|
|
115
|
-
displayName: a.displayName || a.key,
|
|
116
|
-
description: a.description || '',
|
|
117
|
-
color: a.color || 'blue',
|
|
118
|
-
icon: a.icon || '',
|
|
119
|
-
// Trusted-icon gate: only 'user' is untrusted; the EMBEDDED_AGENTS
|
|
120
|
-
// fallback has no origin and is repo-shipped -> 'builtin' is correct.
|
|
121
|
-
origin: a.origin === 'user' ? 'user' : 'builtin',
|
|
122
|
-
order: typeof a.order === 'number' ? a.order : 99,
|
|
123
|
-
domain: typeof a.domain === 'string' && a.domain ? a.domain : 'general',
|
|
124
|
-
connectsTo: a.connectsTo === undefined ? '*' : a.connectsTo,
|
|
125
|
-
produces: Array.isArray(a.produces) ? a.produces : [],
|
|
126
|
-
consumes: Array.isArray(a.consumes) ? a.consumes : [],
|
|
127
|
-
optionalConsumes: Array.isArray(a.optionalConsumes) ? a.optionalConsumes : [],
|
|
128
|
-
}))
|
|
129
|
-
.sort((x, y) => x.order - y.order);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// groupPaletteByDomain(palette, domains) -> ordered [{domain, agents:[...]}].
|
|
133
|
-
// Each group = that domain's own agents PLUS every `shared` agent prepended,
|
|
134
|
-
// then sorted by .order. Pure: no DOM. `domains` is the ordered header list
|
|
135
|
-
// (general last, shared excluded) — see collectDomains / paletteDomains.
|
|
136
|
-
export function groupPaletteByDomain(palette, domains) {
|
|
137
|
-
const list = Array.isArray(palette) ? palette : [];
|
|
138
|
-
const shared = list.filter((a) => a.domain === 'shared');
|
|
139
|
-
const byOrder = (x, y) => x.order - y.order;
|
|
140
|
-
return (Array.isArray(domains) ? domains : []).map((domain) => ({
|
|
141
|
-
domain,
|
|
142
|
-
agents: [...shared, ...list.filter((a) => a.domain === domain)].sort(byOrder),
|
|
143
|
-
}));
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// defaultTopologyFromTemplate(tpl, mk) -> canvas model {steps,feedbacks} with
|
|
147
|
-
// FRESH local ids (mk(key) -> {id,key}). The server template's instance ids
|
|
148
|
-
// (s*_*) are deliberately discarded: once on the canvas, nodes get throwaway
|
|
149
|
-
// local ids and topology() re-stamps contract ids on save. Feedback edges are
|
|
150
|
-
// rewired from server ids to the new local ids by walking the same order.
|
|
151
|
-
export function defaultTopologyFromTemplate(tpl, mk) {
|
|
152
|
-
if (!tpl || !Array.isArray(tpl.steps) || !tpl.steps.length) {
|
|
153
|
-
return { steps: [], feedbacks: [] };
|
|
154
|
-
}
|
|
155
|
-
const remap = {}; // serverId -> localId
|
|
156
|
-
const steps = tpl.steps.map((col) =>
|
|
157
|
-
col.map((node) => {
|
|
158
|
-
const local = mk(node.key);
|
|
159
|
-
remap[node.id] = local.id;
|
|
160
|
-
return local;
|
|
161
|
-
}),
|
|
162
|
-
);
|
|
163
|
-
const feedbacks = (tpl.feedbacks || [])
|
|
164
|
-
.filter((fb) => remap[fb.from] && remap[fb.to])
|
|
165
|
-
.map((fb) => ({ from: remap[fb.from], to: remap[fb.to] }));
|
|
166
|
-
return { steps, feedbacks };
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Channels a pipeline gets without any producing NODE adjacent on the canvas: the
|
|
170
|
-
// user prompt, the shared worktree, the frozen workspace snapshot, and the clarify
|
|
171
|
-
// pre-step. Module-local on purpose — this is NOT channels.mjs PRESEEDED_CHANNELS
|
|
172
|
-
// (that one lists the validator's value-bearing bus seeds and includes
|
|
173
|
-
// plan/checklist; here plan/checklist stay checkable so a producer-less consumer
|
|
174
|
-
// of real content still warns, while this no-build browser module keeps zero
|
|
175
|
-
// imports from src/core).
|
|
176
|
-
const SOFT_PRESEEDED = ['userPrompt', 'code', 'workspace', 'clarify'];
|
|
177
|
-
|
|
178
|
-
// canConnect(fromKey, toKey, agents) -> { ok, reason, warn? }.
|
|
179
|
-
// Governance (connectsTo) is the HARD gate, exactly as before — and an explicit
|
|
180
|
-
// allowlist that admits the target is treated as AUTHOR-CURATED: the soft channel
|
|
181
|
-
// check is skipped (e.g. decomposer -> implementer, where the plan flows through
|
|
182
|
-
// from upstream via the bus). Only wildcard links get the soft pairwise check:
|
|
183
|
-
// when the source produces nothing the target consumes AND the target's required
|
|
184
|
-
// inputs are not all pre-seeded, we return ok:true with a `warn` string the UI
|
|
185
|
-
// surfaces as a toast.
|
|
186
|
-
export function canConnect(fromKey, toKey, agents) {
|
|
187
|
-
const from = agents && agents[fromKey];
|
|
188
|
-
const ct = from ? from.connectsTo : '*';
|
|
189
|
-
if (Array.isArray(ct)) {
|
|
190
|
-
if (!ct.includes(toKey)) {
|
|
191
|
-
const fn = (from && from.displayName) || fromKey;
|
|
192
|
-
const tn = (agents[toKey] && agents[toKey].displayName) || toKey;
|
|
193
|
-
return { ok: false, reason: `${fn} can’t connect to ${tn}` };
|
|
194
|
-
}
|
|
195
|
-
return { ok: true, reason: '' }; // curated allowlist: author vetted this link
|
|
196
|
-
}
|
|
197
|
-
const to = agents && agents[toKey];
|
|
198
|
-
if (from && to && Array.isArray(from.produces) && Array.isArray(to.consumes)
|
|
199
|
-
&& from.produces.length && to.consumes.length) {
|
|
200
|
-
const feeds = from.produces.some((c) => to.consumes.includes(c));
|
|
201
|
-
const required = to.consumes.filter((c) =>
|
|
202
|
-
!(Array.isArray(to.optionalConsumes) && to.optionalConsumes.includes(c)) &&
|
|
203
|
-
!SOFT_PRESEEDED.includes(c));
|
|
204
|
-
if (!feeds && required.length) {
|
|
205
|
-
const fn = from.displayName || fromKey;
|
|
206
|
-
const tn = to.displayName || toKey;
|
|
207
|
-
return { ok: true, reason: '', warn: `${fn} produces [${from.produces.join(', ')}] but ${tn} needs [${required.join(', ')}]` };
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
return { ok: true, reason: '' };
|
|
211
|
-
}
|