feat(capabilities): add claude-orchestration capability (Workflow backend) (#1143)

Default-off, BETA, claude-only capability adopting Claude Code's Workflow tool
(/effort ultracode, Agent SDK >= v0.3.149) as an optional parallel-execution
backend for the GSD loop. Restores the wave parallelism + plan-checker + verifier
that #853 forces inline on Claude Code, and folds gsd-ultraplan-phase under one
runtime gate.

- Pure fail-closed core (src/claude-orchestration.cts): detectWorkflowBackend
  (gate ladder: enabled -> Claude -> backend != inline -> nested+background host
  -> valid Agent SDK -> SDK >= floor; every miss degrades to inline) and
  emitWorkflowScript (waves -> parallel() barriers, plans -> gsd-executor +
  worktree, files_modified overlap -> separate stages, resumeFromRunId, budget).
  All interpolated identifiers validated script-safe; briefs JSON-quoted.
- claude-orchestration command family (gsd-tools claude-orchestration
  detect-backend|emit-workflow) for orchestrator invocation.
- Two gated loop contributions at wired points (execute:wave:post, plan:post);
  federated config keys (enabled/execution_backend/min_agent_sdk_version).
- ADR-1143 implementation amendment; CONTEXT.md glossary entry; explanation doc.

On any runtime lacking the Workflow tool, behaviour is byte-identical to today.

closes #1143
This commit is contained in:
Tom Boucher
2026-07-06 15:18:23 -04:00
parent d01eec4358
commit e3262d94d3
19 changed files with 2357 additions and 9 deletions

View File

@@ -493,7 +493,7 @@ describe('resolveLoopHooks plan:post — pure function against real registry', (
assert.strictEqual(result.activeHooks[0].capId, 'gap-analysis');
});
test('[happy] real registry byLoopPoint plan:post has 1 step (mempalace), 1 contribution (external-job planner fragment), and 1 gate (gap-analysis)', () => {
test('[happy] real registry byLoopPoint plan:post has 1 step (mempalace), 2 contributions (external-job planner + claude-orchestration ultraplan ownership), and 1 gate (gap-analysis)', () => {
const entry = realRegistry.byLoopPoint['plan:post'];
assert.ok(entry, 'plan:post must exist in byLoopPoint');
assert.ok(Array.isArray(entry.steps), 'steps must be an array');
@@ -501,8 +501,12 @@ describe('resolveLoopHooks plan:post — pure function against real registry', (
assert.ok(Array.isArray(entry.gates), 'gates must be an array');
assert.strictEqual(entry.steps.length, 1, 'plan:post must have 1 step (mempalace capture)');
assert.strictEqual(entry.steps[0].capId, 'mempalace', 'plan:post step must be from mempalace');
assert.strictEqual(entry.contributions.length, 1, 'plan:post must have 1 contribution (external-job planner runtime-budget fragment)');
assert.strictEqual(entry.contributions[0].capId, 'external-job', 'plan:post contribution must be from external-job');
// #1143: claude-orchestration registers a plan:post contribution declaring
// ultraplan plan-offload ownership under its runtime gate (default-off).
assert.strictEqual(entry.contributions.length, 2, 'plan:post must have 2 contributions (external-job planner + claude-orchestration ultraplan ownership)');
const capIds = entry.contributions.map(c => c.capId).sort();
assert.deepStrictEqual(capIds, ['claude-orchestration', 'external-job'],
`plan:post contributions must be external-job + claude-orchestration; got ${capIds.join(',')}`);
assert.strictEqual(entry.gates.length, 1, 'plan:post must have exactly one gate');
assert.strictEqual(entry.gates[0].capId, 'gap-analysis');
});