@worca/app 1.0.0 → 1.2.0-rc.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.
Files changed (143) hide show
  1. package/README.md +30 -9
  2. package/agents/clarify.meta.json +4 -4
  3. package/agents/decomposer.meta.json +5 -5
  4. package/agents/implementer.meta.json +15 -5
  5. package/agents/manualTestsChecklist.meta.json +5 -4
  6. package/agents/manualWebUiTesting.meta.json +9 -4
  7. package/agents/planReviewer.meta.json +12 -4
  8. package/agents/planner.meta.json +12 -5
  9. package/agents/refiner.meta.json +15 -4
  10. package/agents/reviewer.meta.json +14 -4
  11. package/agents/worca-cc-clarify.md +7 -0
  12. package/agents/worca-cc-code-reviewer.md +11 -6
  13. package/agents/worca-cc-decomposer.md +7 -0
  14. package/agents/worca-cc-implementer.md +9 -0
  15. package/agents/worca-cc-manual-tests-checklist.md +8 -5
  16. package/agents/worca-cc-manual-web-ui-testing.md +10 -6
  17. package/agents/worca-cc-plan-refiner.md +11 -6
  18. package/agents/worca-cc-plan-reviewer.md +10 -7
  19. package/agents/worca-cc-planner.md +9 -0
  20. package/agents/worca-cc-workspace-reviewer.md +11 -4
  21. package/agents/worca-cc-workspace-scanner.md +8 -4
  22. package/agents/workspaceReviewer.meta.json +15 -4
  23. package/agents/workspaceScanner.meta.json +5 -4
  24. package/package.json +8 -2
  25. package/skills/worca/SKILL.md +5 -5
  26. package/src/cli/render.mjs +148 -0
  27. package/src/cli/worca-cc.mjs +386 -56
  28. package/src/core/agent-gen.mjs +69 -31
  29. package/src/core/agent-registry.mjs +124 -144
  30. package/src/core/agent-store.mjs +164 -4
  31. package/src/core/artifacts.mjs +199 -23
  32. package/src/core/ask/attachment-kind.mjs +95 -0
  33. package/src/core/ask/catalog.mjs +111 -0
  34. package/src/core/ask/comment-deps.mjs +55 -0
  35. package/src/core/ask/events.mjs +545 -0
  36. package/src/core/ask/follow.mjs +113 -0
  37. package/src/core/ask/git-allowlist.mjs +226 -0
  38. package/src/core/ask/limits.mjs +57 -0
  39. package/src/core/ask/mcp-stdio.mjs +135 -0
  40. package/src/core/ask/models.mjs +125 -0
  41. package/src/core/ask/prompt.mjs +286 -0
  42. package/src/core/ask/proposal.mjs +170 -0
  43. package/src/core/ask/redact.mjs +30 -0
  44. package/src/core/ask/spawn.mjs +156 -0
  45. package/src/core/ask/store.mjs +438 -0
  46. package/src/core/ask/tool-deps.mjs +87 -0
  47. package/src/core/ask/tools.mjs +879 -0
  48. package/src/core/ask/turn.mjs +462 -0
  49. package/src/core/ask/worktree-deps.mjs +27 -0
  50. package/src/core/ask/worktrees.mjs +285 -0
  51. package/src/core/chat/command-router.mjs +28 -7
  52. package/src/core/chat/notifier.mjs +6 -1
  53. package/src/core/chat/renderers.mjs +15 -8
  54. package/src/core/claude-runner.mjs +541 -62
  55. package/src/core/config.mjs +310 -44
  56. package/src/core/cost-budget.mjs +29 -2
  57. package/src/core/db.mjs +773 -53
  58. package/src/core/diff-anchor.mjs +213 -0
  59. package/src/core/diff-comments.mjs +273 -0
  60. package/src/core/engine-select.mjs +32 -0
  61. package/src/core/failure-policy.mjs +201 -0
  62. package/src/core/git-info.mjs +49 -10
  63. package/src/core/graph/builtin-workflows.mjs +51 -0
  64. package/src/core/graph/executor.mjs +894 -0
  65. package/src/core/graph/registry-ports.mjs +12 -0
  66. package/src/core/graph/scheduler.mjs +1072 -0
  67. package/src/core/graph/seed-templates.mjs +318 -0
  68. package/src/core/host-guard.mjs +271 -0
  69. package/src/core/model-env.mjs +180 -8
  70. package/src/core/model-test.mjs +79 -0
  71. package/src/core/orchestrator.mjs +994 -4097
  72. package/src/core/overview-agent.mjs +15 -3
  73. package/src/core/phases.mjs +208 -537
  74. package/src/core/pipeline-delete.mjs +13 -2
  75. package/src/core/plugin-api.mjs +8 -3
  76. package/src/core/plugin-config.mjs +178 -28
  77. package/src/core/plugin-inventory.mjs +6 -2
  78. package/src/core/plugin-manifest.mjs +199 -11
  79. package/src/core/plugin-models.mjs +1 -0
  80. package/src/core/plugin-repo.mjs +16 -4
  81. package/src/core/plugin-shim-child.mjs +9 -3
  82. package/src/core/plugin-shim.mjs +80 -17
  83. package/src/core/plugin-store.mjs +236 -29
  84. package/src/core/plugin-workflows.mjs +90 -41
  85. package/src/core/preflight.mjs +135 -3
  86. package/src/core/projects.mjs +7 -5
  87. package/src/core/protocol.mjs +8 -35
  88. package/src/core/recoverable-error.mjs +1 -1
  89. package/src/core/run-harness.mjs +3934 -0
  90. package/src/core/run-manifest.mjs +5 -1
  91. package/src/core/settings.mjs +184 -13
  92. package/src/core/skills.mjs +10 -3
  93. package/src/core/source-bindings.mjs +175 -0
  94. package/src/core/sources.mjs +87 -25
  95. package/src/core/stats.mjs +25 -6
  96. package/src/core/title.mjs +51 -4
  97. package/src/core/workflows.mjs +358 -259
  98. package/src/core/workspace-scan.mjs +4 -0
  99. package/src/core/worktree.mjs +98 -7
  100. package/src/shared/graph/agent-meta.mjs +278 -0
  101. package/src/shared/graph/constants.mjs +105 -0
  102. package/src/shared/graph/geometry.mjs +157 -0
  103. package/src/shared/graph/layout.mjs +134 -0
  104. package/src/shared/graph/loops.mjs +130 -0
  105. package/src/shared/graph/manifest.mjs +257 -0
  106. package/src/shared/graph/ports.mjs +153 -0
  107. package/src/shared/graph/route.mjs +397 -0
  108. package/src/shared/graph/template.mjs +165 -0
  109. package/src/shared/graph/thumbnail.mjs +67 -0
  110. package/src/shared/graph/validate.mjs +491 -0
  111. package/src/shared/graph/verdict.mjs +41 -0
  112. package/ui/public/app.js +4240 -1682
  113. package/ui/public/ask-markdown.mjs +145 -0
  114. package/ui/public/ask-model.mjs +317 -0
  115. package/ui/public/ask-panel.mjs +2129 -0
  116. package/ui/public/chat-settings-view.mjs +6 -2
  117. package/ui/public/diff-view.mjs +66 -11
  118. package/ui/public/file-tree.mjs +305 -0
  119. package/ui/public/graph/composer.mjs +889 -0
  120. package/ui/public/graph/inspector.mjs +183 -0
  121. package/ui/public/graph/model.mjs +37 -0
  122. package/ui/public/graph/palette.mjs +144 -0
  123. package/ui/public/graph/run-decor.mjs +410 -0
  124. package/ui/public/graph/run-hosts.mjs +201 -0
  125. package/ui/public/graph/save-dialog.mjs +56 -0
  126. package/ui/public/graph/view.mjs +858 -0
  127. package/ui/public/guardrails-view.mjs +4 -2
  128. package/ui/public/hljs-loader.mjs +180 -0
  129. package/ui/public/index.html +311 -265
  130. package/ui/public/log-filter.mjs +22 -4
  131. package/ui/public/log-line.mjs +45 -19
  132. package/ui/public/models-view.mjs +171 -9
  133. package/ui/public/plugins-view.mjs +106 -4
  134. package/ui/public/source-pane.mjs +190 -8
  135. package/ui/public/stats-view.mjs +81 -1
  136. package/ui/public/style.css +1487 -229
  137. package/ui/public/syntax-highlight.mjs +270 -0
  138. package/ui/public/thinking-orb.mjs +110 -0
  139. package/ui/server.mjs +1894 -104
  140. package/src/core/channels.mjs +0 -302
  141. package/src/core/runners.mjs +0 -167
  142. package/src/core/workflow-validator.mjs +0 -185
  143. package/ui/public/composer-core.mjs +0 -211
@@ -0,0 +1,165 @@
1
+ // src/shared/graph/template.mjs
2
+ // The editable v2 template: normalize/serialize, the node/wire factory and the
3
+ // drop-legality check the composer runs on the POINTER PATH (so it must stay a
4
+ // Map lookup plus a type check — never a Tarjan walk).
5
+ import { TEMPLATE_VERSION } from './constants.mjs';
6
+ import { portsOf, findPort, resolveOrOutType, inboundWires } from './ports.mjs';
7
+
8
+ const isObject = (v) => Boolean(v) && typeof v === 'object' && !Array.isArray(v);
9
+ const ARITY_SET = new Set(['and', 'or', 'combine']);
10
+
11
+ /** Canonicalize an authored or fetched template: numeric coordinates, a
12
+ * materialized `config` on every node, non-object nodes/wires dropped, `key`
13
+ * kept only where V3 allows it. Unknown config keys are PRESERVED (V17 warns
14
+ * and ignores them; it never strips them). */
15
+ export function normalizeTemplate(raw) {
16
+ const t = isObject(raw) ? raw : {};
17
+ const template = {
18
+ id: String(t.id ?? ''),
19
+ name: String(t.name ?? ''),
20
+ version: TEMPLATE_VERSION,
21
+ domain: String(t.domain ?? ''),
22
+ nodes: (Array.isArray(t.nodes) ? t.nodes : []).filter(isObject).map(normalizeNode),
23
+ wires: (Array.isArray(t.wires) ? t.wires : []).filter(isObject).map(normalizeWire),
24
+ };
25
+ const canvas = normalizeCanvas(t.canvas);
26
+ if (canvas) template.canvas = canvas; // view state, engine-ignored — but it round-trips
27
+ return template;
28
+ }
29
+
30
+ /** The POST body: normalized, stable key order, stripped of anything JSON cannot carry. */
31
+ export function serializeTemplate(template) {
32
+ return JSON.parse(JSON.stringify(normalizeTemplate(template)));
33
+ }
34
+
35
+ function normalizeNode(node) {
36
+ const out = { id: String(node.id ?? ''), kind: String(node.kind ?? '') };
37
+ if (out.kind === 'agent' && node.key !== undefined) out.key = String(node.key);
38
+ out.x = Number(node.x);
39
+ out.y = Number(node.y);
40
+ out.config = isObject(node.config) ? { ...node.config } : {};
41
+ return out;
42
+ }
43
+
44
+ function normalizeWire(wire) {
45
+ const out = {
46
+ id: String(wire.id ?? ''),
47
+ from: { node: String(wire.from?.node ?? ''), port: String(wire.from?.port ?? '') },
48
+ to: { node: String(wire.to?.node ?? ''), port: String(wire.to?.port ?? '') },
49
+ };
50
+ if (isObject(wire.config) && Object.keys(wire.config).length) out.config = { ...wire.config };
51
+ return out;
52
+ }
53
+
54
+ /** All three fields or nothing — a half-written canvas is worse than none. */
55
+ function normalizeCanvas(canvas) {
56
+ if (!isObject(canvas)) return null;
57
+ const { x, y, zoom } = canvas;
58
+ if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(zoom)) return null;
59
+ return { x, y, zoom };
60
+ }
61
+
62
+ /** `n_`/`w_` + 8 base36 chars, re-drawn until it misses `taken` (a Set of ids). */
63
+ export function mintId(prefix, taken) {
64
+ const used = taken instanceof Set ? taken : new Set(Array.isArray(taken) ? taken : []);
65
+ for (let attempt = 0; attempt < 50; attempt += 1) {
66
+ let body = '';
67
+ while (body.length < 8) body += Math.random().toString(36).slice(2);
68
+ const id = `${prefix}${body.slice(0, 8)}`;
69
+ if (!used.has(id)) return id;
70
+ }
71
+ return `${prefix}${Date.now().toString(36).slice(-8)}`; // unreachable in practice
72
+ }
73
+
74
+ /** @param {'agent'|'task'|'end'|'and'|'or'|'combine'} kind @param {string|null} key */
75
+ export function newNode(kind, key, x = 0, y = 0, taken) {
76
+ const node = { id: mintId('n_', taken), kind };
77
+ if (kind === 'agent' && key) node.key = String(key); // V3: only agent nodes carry a key
78
+ node.x = Number(x) || 0;
79
+ node.y = Number(y) || 0;
80
+ node.config = ARITY_SET.has(kind) ? { arity: 2 } : {};
81
+ return node;
82
+ }
83
+
84
+ export function newWire(from, to, config, taken) {
85
+ const wire = {
86
+ id: mintId('w_', taken),
87
+ from: { node: from.node, port: from.port },
88
+ to: { node: to.node, port: to.port },
89
+ };
90
+ if (isObject(config) && Object.keys(config).length) wire.config = { ...config };
91
+ return wire;
92
+ }
93
+
94
+ /**
95
+ * Drop legality, derived entirely from the template — which is what lets it see
96
+ * or-homogeneity and existing wires at all. Reasons, in check order:
97
+ * unknown port — the endpoint does not resolve (V5)
98
+ * same node — an output and an input of the SAME card, UNLESS
99
+ * the pair is the self-loop the engine runs: a
100
+ * `when:'blocking'` output into a `loop:true`
101
+ * input (the seeded refiner `revise → revise`).
102
+ * Any other same-card pair is refused here rather
103
+ * than left to V10/V11 after the drop. Checked
104
+ * after the ports resolve because it reads their
105
+ * meta; `V0` is the chip's code, not a validator
106
+ * rule.
107
+ * already connected — UNIFORM single-wire (V7): ANY wired input rejects,
108
+ * agent ports, or `inK`, `end.result` and `await`
109
+ * alike. A duplicate (from,to) pair is necessarily a
110
+ * second wire into a wired input, so it lands here
111
+ * too. Rewiring is remove-then-drop.
112
+ * <out> → <in> type mismatch — per-wire legality (V8); `any` accepts everything
113
+ * or inputs must match: <t> — or homogeneity (V12), mirrored from the resolved
114
+ * payload type; an or accepts ANY type until one
115
+ * in-wire exists.
116
+ * @returns {{ok:true} | {ok:false, code:string, reason:string}}
117
+ */
118
+ export function canWire({ tpl, portsFn, from, to }) {
119
+ const nodes = Array.isArray(tpl?.nodes) ? tpl.nodes : [];
120
+ const byId = new Map(nodes.filter(isObject).map((n) => [n.id, n]));
121
+ const source = byId.get(from?.node);
122
+ const target = byId.get(to?.node);
123
+ const outPort = source ? findPort(portsOf(portsFn, source), from.port, 'out') : null;
124
+ const inPort = target ? findPort(portsOf(portsFn, target), to.port, 'in') : null;
125
+ if (!outPort || !inPort) return deny('V5', 'unknown port');
126
+ const selfLoop = outPort.when === 'blocking' && inPort.loop === true;
127
+ if (source.id === target.id && !selfLoop) return deny('V0', 'same node');
128
+ if (inboundWires(tpl, to.node, to.port).length) return deny('V7', 'already connected');
129
+
130
+ const sourceType = outPort.type && outPort.type !== 'any'
131
+ ? outPort.type
132
+ : (source.kind === 'or' ? resolveOrOutType(tpl, portsFn, source.id) : outPort.type ?? null);
133
+ if (inPort.type !== 'any' && sourceType !== null && sourceType !== inPort.type) {
134
+ return deny('V8', `${sourceType} → ${inPort.type} type mismatch`);
135
+ }
136
+ if (target.kind === 'or') {
137
+ const resolved = resolveOrOutType(tpl, portsFn, target.id);
138
+ if (resolved !== null && sourceType !== null && sourceType !== resolved) {
139
+ return deny('V12', `or inputs must match: ${resolved}`);
140
+ }
141
+ }
142
+ return { ok: true };
143
+ }
144
+
145
+ const deny = (code, reason) => ({ ok: false, code, reason });
146
+
147
+ /** Structural edits — always a NEW template; the input is never mutated. */
148
+ export function removeNode(tpl, nodeId) {
149
+ const t = normalizeTemplate(tpl);
150
+ return { ...t, nodes: t.nodes.filter((n) => n.id !== nodeId),
151
+ wires: t.wires.filter((w) => w.from.node !== nodeId && w.to.node !== nodeId) };
152
+ }
153
+
154
+ export function removeWire(tpl, wireId) {
155
+ const t = normalizeTemplate(tpl);
156
+ return { ...t, wires: t.wires.filter((w) => w.id !== wireId) };
157
+ }
158
+
159
+ export function nodeById(tpl, id) {
160
+ return (Array.isArray(tpl?.nodes) ? tpl.nodes : []).find((n) => n?.id === id) || null;
161
+ }
162
+
163
+ export function wireById(tpl, id) {
164
+ return (Array.isArray(tpl?.wires) ? tpl.wires : []).find((w) => w?.id === id) || null;
165
+ }
@@ -0,0 +1,67 @@
1
+ // src/shared/graph/thumbnail.mjs
2
+ // A v2 template -> a mini-SVG string for the saved-pipeline rows. Pure and
3
+ // deterministic, and the markup carries NUMBERS ONLY (no ids, no names, no
4
+ // author text), so the result is safe to hand to innerHTML without escaping.
5
+ // The whole scene is drawn in WORLD space inside one <g transform>, which is
6
+ // what lets it reuse the real router instead of a second wire geometry.
7
+ import { graphBounds, fitBounds, nodeSize, portAnchor } from './geometry.mjs';
8
+ import { routeAll, routePathD } from './route.mjs';
9
+ import { portsOf, findPort } from './ports.mjs';
10
+
11
+ const DEFAULTS = { width: 120, height: 64, pad: 8, radius: 3 };
12
+ const round = (v) => Math.round(v * 100) / 100;
13
+ /** A drawable node: an object with a string id. A truthy non-object survived
14
+ * `filter(Boolean)` and indexed under `undefined`, which is what let a
15
+ * non-object wire find a `from` and throw on `w.from.port` below. */
16
+ const isNode = (n) => Boolean(n) && typeof n === 'object' && !Array.isArray(n) && typeof n.id === 'string';
17
+
18
+ export function thumbnailSvg(tpl, portsFn, opts = {}) {
19
+ const { width, height, pad, radius } = { ...DEFAULTS, ...opts };
20
+ const nodes = (Array.isArray(tpl?.nodes) ? tpl.nodes : []).filter(isNode);
21
+ const open = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" `
22
+ + `viewBox="0 0 ${width} ${height}" role="img" aria-hidden="true">`;
23
+ if (!nodes.length) return `${open}</svg>`;
24
+
25
+ const byId = new Map(nodes.map((n) => [n.id, n]));
26
+ // The cards are the router's obstacles, so the tile shows the same shapes the
27
+ // live canvas does — one wire, one <path>, no arrow markers, no split legs.
28
+ const obstacles = nodes.map((n) => ({ x: Number(n.x) || 0, y: Number(n.y) || 0, ...nodeSize(n, portsOf(portsFn, n)) }));
29
+ const wireList = [];
30
+ for (const w of (Array.isArray(tpl?.wires) ? tpl.wires : [])) {
31
+ const from = byId.get(w?.from?.node);
32
+ const to = byId.get(w?.to?.node);
33
+ if (!from || !to) continue; // dangling (V5) — never draw NaN
34
+ const fromPorts = portsOf(portsFn, from);
35
+ const toPorts = portsOf(portsFn, to);
36
+ if (!findPort(fromPorts, w.from.port, 'out') || !findPort(toPorts, w.to.port, 'in')) continue;
37
+ const a = portAnchor(from, fromPorts, w.from.port, 'out');
38
+ const b = portAnchor(to, toPorts, w.to.port, 'in');
39
+ if (a && b) wireList.push({ id: String(w.id), a, b }); // ids stay INTERNAL: route keys only
40
+ }
41
+ const { routes } = routeAll(wireList, obstacles);
42
+
43
+ // Measured over the DRAWN set, never the raw one: a junk entry the loop above
44
+ // skips must not stretch the fit that positions the cards it does draw. The
45
+ // routed vertices join the union so a detour is never clipped out of the tile.
46
+ const base = graphBounds({ ...tpl, nodes }, portsFn, { pad: 0 });
47
+ let x0 = base.x; let y0 = base.y; let x1 = base.x + base.w; let y1 = base.y + base.h;
48
+ for (const pts of routes.values()) {
49
+ for (const p of pts) { x0 = Math.min(x0, p.x); y0 = Math.min(y0, p.y); x1 = Math.max(x1, p.x); y1 = Math.max(y1, p.y); }
50
+ }
51
+ const bounds = { x: x0 - pad, y: y0 - pad, w: x1 - x0 + 2 * pad, h: y1 - y0 + 2 * pad };
52
+ const { z, tx, ty } = fitBounds(bounds, { width, height }, { zoomMin: 0, zoomMax: 1 });
53
+ const stroke = round(1 / (z || 1));
54
+
55
+ // Wires first so the cards sit on top, exactly like the live canvas.
56
+ const paths = [...routes.values()].map((pts) =>
57
+ `<path d="${routePathD(pts)}" fill="none" stroke="#B7B7BC" stroke-width="${stroke}" stroke-linejoin="round"/>`).join('');
58
+
59
+ const rects = nodes.map((node) => {
60
+ const size = nodeSize(node, portsOf(portsFn, node));
61
+ return `<rect x="${round(Number(node.x) || 0)}" y="${round(Number(node.y) || 0)}" `
62
+ + `width="${round(size.w)}" height="${round(size.h)}" rx="${radius}" `
63
+ + `fill="#FFFFFF" stroke="#C9C9CE" stroke-width="${stroke}"/>`;
64
+ }).join('');
65
+
66
+ return `${open}<g transform="translate(${round(tx)} ${round(ty)}) scale(${round(z)})">${paths}${rects}</g></svg>`;
67
+ }