@worca/ui 0.27.0 → 0.28.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.
@@ -0,0 +1,31 @@
1
+ [
2
+ { "name": "batch", "group": "Built-in" },
3
+ { "name": "claude-api", "group": "Built-in" },
4
+ { "name": "debug", "group": "Built-in" },
5
+ { "name": "fewer-permission-prompts", "group": "Built-in" },
6
+ { "name": "init", "group": "Built-in" },
7
+ { "name": "loop", "group": "Built-in" },
8
+ { "name": "review", "group": "Built-in" },
9
+ { "name": "schedule", "group": "Built-in" },
10
+ { "name": "security-review", "group": "Built-in" },
11
+ { "name": "simplify", "group": "Built-in" },
12
+ { "name": "update-config", "group": "Built-in" },
13
+ { "name": "hookify:hookify", "group": "Plugin" },
14
+ { "name": "hookify:configure", "group": "Plugin" },
15
+ { "name": "hookify:list", "group": "Plugin" },
16
+ { "name": "hookify:writing-rules", "group": "Plugin" },
17
+ { "name": "feature-dev:feature-dev", "group": "Plugin" },
18
+ { "name": "feature-dev:code-reviewer", "group": "Plugin" },
19
+ { "name": "feature-dev:code-architect", "group": "Plugin" },
20
+ { "name": "feature-dev:code-explorer", "group": "Plugin" },
21
+ { "name": "claude-md-management:revise-claude-md", "group": "Plugin" },
22
+ { "name": "claude-md-management:claude-md-improver", "group": "Plugin" },
23
+ { "name": "worca-install", "group": "Worca" },
24
+ { "name": "worca-rc", "group": "Worca" },
25
+ { "name": "worca-release", "group": "Worca" },
26
+ { "name": "worca-sync", "group": "Worca" },
27
+ { "name": "worca-sync-pr", "group": "Worca" },
28
+ { "name": "worca-sync-commit", "group": "Worca" },
29
+ { "name": "worca-analyze", "group": "Worca" },
30
+ { "name": "worca-agent-override", "group": "Worca" }
31
+ ]
@@ -0,0 +1,18 @@
1
+ [
2
+ { "name": "Agent", "group": "Core" },
3
+ { "name": "Bash", "group": "Core" },
4
+ { "name": "Edit", "group": "Core" },
5
+ { "name": "Glob", "group": "Core" },
6
+ { "name": "Grep", "group": "Core" },
7
+ { "name": "Read", "group": "Core" },
8
+ { "name": "Write", "group": "Core" },
9
+ { "name": "Skill", "group": "Core" },
10
+ { "name": "WebFetch", "group": "Core" },
11
+ { "name": "WebSearch", "group": "Core" },
12
+ { "name": "NotebookEdit", "group": "Core" },
13
+ { "name": "EnterPlanMode", "group": "Mode" },
14
+ { "name": "EnterWorktree", "group": "Mode" },
15
+ { "name": "TodoWrite", "group": "Task" },
16
+ { "name": "TaskCreate", "group": "Task" },
17
+ { "name": "TaskUpdate", "group": "Task" }
18
+ ]
@@ -23,7 +23,9 @@ import { actionAllowed } from '../app/utils/state-actions.js';
23
23
  import { atomicWriteSync } from './atomic-write.js';
24
24
  import { dbExists, getIssue, listIssues } from './beads-reader.js';
25
25
  import { dispatchExternal } from './dispatch-external.js';
26
+ import { migrateDispatchGovernance } from './dispatch-migration.js';
26
27
  import { ensureWebhookForUi } from './ensure-webhook.js';
28
+ import { getDefaultBranch } from './git-helpers.js';
27
29
  import { extractAndStripGlobalKeys } from './global-keys.js';
28
30
  import { LaunchLock } from './launch-lock.js';
29
31
  import { createModelEnvRouter } from './model-env-routes.js';
@@ -361,7 +363,8 @@ export function createProjectScopedRoutes({
361
363
  router.get('/runs', requireWorcaDir, (req, res) => {
362
364
  try {
363
365
  const runs = discoverRuns(req.project.worcaDir);
364
- const response = { ok: true, runs };
366
+ const default_branch = getDefaultBranch(req.project.projectRoot);
367
+ const response = { ok: true, runs, default_branch };
365
368
  // Include settings so multi-project clients can use loop limits, etc.
366
369
  const { settingsPath } = req.project;
367
370
  if (settingsPath && existsSync(settingsPath)) {
@@ -515,22 +518,15 @@ export function createProjectScopedRoutes({
515
518
  if (!base.worca || typeof base.worca !== 'object') base.worca = {};
516
519
  base.worca = deepMerge(base.worca, body.worca);
517
520
 
518
- if (
519
- body.worca.governance &&
520
- typeof body.worca.governance === 'object' &&
521
- body.worca.governance.subagent_dispatch !== undefined &&
522
- base.worca.governance &&
523
- typeof base.worca.governance === 'object' &&
524
- 'dispatch' in base.worca.governance
525
- ) {
526
- delete base.worca.governance.dispatch;
527
- }
528
521
  baseChanged = true;
529
522
  }
530
523
 
531
524
  // STEP 1: extract misplaced global keys + inert milestone keys
532
525
  const autoMigrated = extractAndStripGlobalKeys(base);
533
526
 
527
+ // STEP 1a: migrate legacy subagent_dispatch → dispatch.subagents (W-054)
528
+ if (base.worca) migrateDispatchGovernance(base.worca);
529
+
534
530
  // STEP 2: write extracted global keys to ~/.worca/settings.json
535
531
  const globalSettingsPath = prefsDir
536
532
  ? join(prefsDir, 'settings.json')
@@ -396,19 +396,91 @@ export function validateSettingsPayload(body, options = {}) {
396
396
  ) {
397
397
  details.push('governance.dispatch must be an object');
398
398
  } else {
399
+ const DISPATCH_SECTIONS = ['tools', 'skills', 'subagents'];
399
400
  for (const [key, val] of Object.entries(g.dispatch)) {
400
- if (!VALID_AGENTS.includes(key)) {
401
- details.push(`Unknown dispatch agent: "${key}"`);
402
- continue;
403
- }
404
- if (!Array.isArray(val)) {
405
- details.push(`Dispatch for "${key}" must be an array`);
406
- continue;
407
- }
408
- for (const v of val) {
409
- if (typeof v !== 'string') {
410
- details.push(`Dispatch entry for "${key}" must be a string`);
401
+ if (DISPATCH_SECTIONS.includes(key)) {
402
+ // W-054 nested shape: dispatch.{tools,skills,subagents}
403
+ if (
404
+ typeof val !== 'object' ||
405
+ val === null ||
406
+ Array.isArray(val)
407
+ ) {
408
+ details.push(`governance.dispatch.${key} must be an object`);
409
+ continue;
410
+ }
411
+ for (const tierKey of ['always_disallowed', 'default_denied']) {
412
+ if (val[tierKey] === undefined) continue;
413
+ if (!Array.isArray(val[tierKey])) {
414
+ details.push(
415
+ `governance.dispatch.${key}.${tierKey} must be an array`,
416
+ );
417
+ continue;
418
+ }
419
+ for (const entry of val[tierKey]) {
420
+ if (typeof entry !== 'string') {
421
+ details.push(
422
+ `governance.dispatch.${key}.${tierKey} entries must be strings`,
423
+ );
424
+ }
425
+ }
426
+ }
427
+ if (val.per_agent_allow !== undefined) {
428
+ if (
429
+ typeof val.per_agent_allow !== 'object' ||
430
+ val.per_agent_allow === null ||
431
+ Array.isArray(val.per_agent_allow)
432
+ ) {
433
+ details.push(
434
+ `governance.dispatch.${key}.per_agent_allow must be an object`,
435
+ );
436
+ } else {
437
+ for (const [agent, allowList] of Object.entries(
438
+ val.per_agent_allow,
439
+ )) {
440
+ if (
441
+ agent !== '_defaults' &&
442
+ !VALID_AGENTS.includes(agent) &&
443
+ agent !== 'workspace_planner'
444
+ ) {
445
+ details.push(
446
+ `governance.dispatch.${key}.per_agent_allow: unknown agent "${agent}"`,
447
+ );
448
+ continue;
449
+ }
450
+ if (!Array.isArray(allowList)) {
451
+ details.push(
452
+ `governance.dispatch.${key}.per_agent_allow.${agent} must be an array`,
453
+ );
454
+ continue;
455
+ }
456
+ for (const entry of allowList) {
457
+ if (typeof entry !== 'string') {
458
+ details.push(
459
+ `governance.dispatch.${key}.per_agent_allow.${agent} entries must be strings`,
460
+ );
461
+ }
462
+ }
463
+ }
464
+ }
465
+ }
466
+ } else if (
467
+ VALID_AGENTS.includes(key) ||
468
+ key === 'workspace_planner'
469
+ ) {
470
+ // Pre-W-054 legacy flat shape — tolerated for migration.
471
+ if (!Array.isArray(val)) {
472
+ details.push(`Dispatch for "${key}" must be an array`);
473
+ continue;
474
+ }
475
+ for (const v of val) {
476
+ if (typeof v !== 'string') {
477
+ details.push(
478
+ `Dispatch entry for "${key}" must be a string`,
479
+ );
480
+ }
411
481
  }
482
+ } else {
483
+ details.push(`Unknown dispatch key: "${key}"`);
412
484
  }
413
485
  }
414
486
  }
package/server/watcher.js CHANGED
@@ -157,6 +157,7 @@ export function discoverRuns(worcaDir) {
157
157
  ...status,
158
158
  worktree_worca_dir: join(reg.worktree_path, '.worca'),
159
159
  is_worktree_run: true,
160
+ head_branch: reg.branch || null,
160
161
  fleet_id: reg.fleet_id || null,
161
162
  workspace_id: reg.workspace_id || null,
162
163
  group_type: reg.group_type || null,
@@ -311,6 +312,7 @@ export async function discoverRunsAsync(worcaDir) {
311
312
  ...status,
312
313
  worktree_worca_dir: join(reg.worktree_path, '.worca'),
313
314
  is_worktree_run: true,
315
+ head_branch: reg.branch || null,
314
316
  fleet_id: reg.fleet_id || null,
315
317
  workspace_id: reg.workspace_id || null,
316
318
  group_type: reg.group_type || null,
@@ -20,6 +20,7 @@ import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
20
20
  import * as fsp from 'node:fs/promises';
21
21
  import { join } from 'node:path';
22
22
  import { Router } from 'express';
23
+ import { getDefaultBranch } from './git-helpers.js';
23
24
  import { pruneWorktrees, removeWorktree } from './worktree-ops.js';
24
25
 
25
26
  const CLEANUP_CONCURRENCY = 4;
@@ -362,6 +363,7 @@ async function _listWorktrees(worcaDir) {
362
363
  started_at: m.reg.started_at || null,
363
364
  status: m.status,
364
365
  removable: m.status !== 'running',
366
+ target_branch: m.reg.target_branch || null,
365
367
  fleet_id: m.reg.fleet_id || null,
366
368
  workspace_id: m.reg.workspace_id || null,
367
369
  group_type: m.reg.group_type || null,
@@ -399,9 +401,11 @@ export function createWorktreesRouter() {
399
401
  }
400
402
  try {
401
403
  const worktrees = await _listWorktrees(worcaDir);
404
+ const default_branch = getDefaultBranch(req.project?.projectRoot);
402
405
  res.json({
403
406
  ok: true,
404
407
  worktrees,
408
+ default_branch,
405
409
  // Documents the semantics shift in `disk_bytes` (project files only).
406
410
  // Clients can render this as a caveat next to disk totals.
407
411
  disk_walk_skip_dirs: [...WALK_SKIP_DIRS],