@williambeto/ai-workflow 2.9.2 โ†’ 2.9.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  ## [unreleased]
2
2
 
3
+ ## [2.9.3] - 2026-07-16
4
+
5
+ ### ๐Ÿ› Bug Fixes
6
+
7
+ - Restore scoped specialist delegation for every core AIWK agent without
8
+ reintroducing `bash` or `edit` approval prompts
9
+ - Allow Atlas to reach both workflow agents and all registered specialists
10
+ while preventing specialists from delegating recursively
11
+ - Set OpenCode's bounded subagent depth to two so workflow agents delegated by
12
+ Atlas can still consult one specialist
13
+ - Detect stale or incomplete specialist routing through `ai-workflow doctor`
14
+
15
+ ### ๐Ÿงช Testing
16
+
17
+ - Cover the complete six-caller by twenty-specialist routing matrix
18
+ - Verify upgrades from permission-free `2.9.2` configurations and reject
19
+ recursive specialist delegation
20
+ - Exercise Atlas to workflow-agent to specialist routing in a real OpenCode
21
+ runtime smoke test
22
+
3
23
  ## [2.9.2] - 2026-07-16
4
24
 
5
25
  ### ๐Ÿ› Bug Fixes
@@ -761,6 +761,58 @@ var SUBAGENTS = {
761
761
  "Technical-Leader": { description: "Capability only: analyze technical decisions, architecture, and trade-offs", skill: "technical-leadership" },
762
762
  "UI-UX-Engineer": { description: "Capability only: review usability and interface clarity", skill: "ui-ux-design" }
763
763
  };
764
+ var CORE_AGENT_NAMES = Object.freeze(Object.keys(AGENTS));
765
+ var SPECIALIST_AGENT_NAMES = Object.freeze(Object.keys(SUBAGENTS));
766
+ var MIN_SUBAGENT_DEPTH = 2;
767
+ function inspectSpecialistRoutingConfig(opencodeConfig) {
768
+ const errors = [];
769
+ const warnings = [];
770
+ const agents = opencodeConfig?.agent ?? {};
771
+ if (typeof opencodeConfig?.subagent_depth !== "number" || opencodeConfig.subagent_depth < MIN_SUBAGENT_DEPTH) {
772
+ errors.push(`OpenCode subagent_depth must be at least ${MIN_SUBAGENT_DEPTH} for workflow-agent to specialist routing`);
773
+ }
774
+ for (const specialist of SPECIALIST_AGENT_NAMES) {
775
+ if (agents[specialist]?.mode !== "subagent") {
776
+ errors.push(`specialist missing or not configured as subagent: ${specialist}`);
777
+ }
778
+ if (agents[specialist]?.permission?.task !== void 0) {
779
+ errors.push(`specialist must not delegate recursively: ${specialist}`);
780
+ }
781
+ }
782
+ for (const caller of CORE_AGENT_NAMES) {
783
+ const taskPermissions = agents[caller]?.permission?.task;
784
+ if (!taskPermissions || typeof taskPermissions !== "object" || Array.isArray(taskPermissions)) {
785
+ errors.push(`core agent lacks explicit specialist task routing: ${caller}`);
786
+ continue;
787
+ }
788
+ if (taskPermissions["*"] !== "deny") {
789
+ errors.push(`core agent task routing must deny unspecified targets: ${caller}`);
790
+ }
791
+ if (Object.values(taskPermissions).includes("ask")) {
792
+ errors.push(`core agent task routing must not request approval: ${caller}`);
793
+ }
794
+ const blockedSpecialists = SPECIALIST_AGENT_NAMES.filter((specialist) => taskPermissions[specialist] !== "allow");
795
+ if (blockedSpecialists.length > 0) {
796
+ errors.push(`core agent cannot reach all specialists: ${caller} (${blockedSpecialists.join(", ")})`);
797
+ }
798
+ const otherCoreAgents = CORE_AGENT_NAMES.filter((name) => name !== caller);
799
+ if (caller === "Atlas") {
800
+ const blockedCoreAgents = otherCoreAgents.filter((name) => taskPermissions[name] !== "allow");
801
+ if (blockedCoreAgents.length > 0) {
802
+ errors.push(`Atlas cannot reach all workflow agents: ${blockedCoreAgents.join(", ")}`);
803
+ }
804
+ } else {
805
+ const recursiveCoreAgents = otherCoreAgents.filter((name) => taskPermissions[name] === "allow");
806
+ if (recursiveCoreAgents.length > 0) {
807
+ errors.push(`non-Atlas core agent can recursively delegate workflow ownership: ${caller} (${recursiveCoreAgents.join(", ")})`);
808
+ }
809
+ }
810
+ }
811
+ if (opencodeConfig?.permission?.task !== void 0) {
812
+ warnings.push("consumer-level permission.task may override AIWK specialist routing or enable recursive delegation");
813
+ }
814
+ return { valid: errors.length === 0, errors, warnings };
815
+ }
764
816
  var COMMANDS = {
765
817
  atlas: { description: "Decide safest next step and delegate to the appropriate workflow or agent", agent: "Atlas" },
766
818
  run: { description: "Execute the master orchestration pipeline (Gates, SDD, Implementation, Validation, Healing)", agent: "Atlas" },
@@ -779,11 +831,23 @@ var COMMANDS = {
779
831
  };
780
832
  function buildManagedConfig() {
781
833
  const agent = {};
834
+ const specialistTaskPermissions = Object.fromEntries([
835
+ ["*", "deny"],
836
+ ...SPECIALIST_AGENT_NAMES.map((name) => [name, "allow"])
837
+ ]);
838
+ const atlasTaskPermissions = Object.fromEntries([
839
+ ["*", "deny"],
840
+ ...CORE_AGENT_NAMES.filter((name) => name !== "Atlas").map((name) => [name, "allow"]),
841
+ ...SPECIALIST_AGENT_NAMES.map((name) => [name, "allow"])
842
+ ]);
782
843
  for (const [name, def] of Object.entries(AGENTS)) {
783
844
  agent[name] = {
784
845
  mode: def.mode,
785
846
  description: def.description,
786
- prompt: `{file:./.ai-workflow/opencode/agents/${name.toLowerCase()}.md}`
847
+ prompt: `{file:./.ai-workflow/opencode/agents/${name.toLowerCase()}.md}`,
848
+ permission: {
849
+ task: name === "Atlas" ? atlasTaskPermissions : specialistTaskPermissions
850
+ }
787
851
  };
788
852
  }
789
853
  for (const [name, def] of Object.entries(SUBAGENTS)) {
@@ -804,6 +868,7 @@ function buildManagedConfig() {
804
868
  return {
805
869
  $schema: "https://opencode.ai/config.json",
806
870
  default_agent: "Atlas",
871
+ subagent_depth: MIN_SUBAGENT_DEPTH,
807
872
  agent,
808
873
  skills: {
809
874
  paths: ["./.ai-workflow/opencode/skills"]
@@ -839,6 +904,7 @@ async function mergeOpencodeConfig(cwd, { force = false, backupRoot = ".ai-workf
839
904
  const next = {
840
905
  ...currentWithoutLegacy,
841
906
  $schema: current.$schema ?? managed.$schema,
907
+ subagent_depth: typeof current.subagent_depth === "number" ? Math.max(current.subagent_depth, managed.subagent_depth) : managed.subagent_depth,
842
908
  ...managed.mcp ? {
843
909
  mcp: {
844
910
  ...current.mcp ?? {},
@@ -1988,6 +2054,18 @@ async function checkOpencodeJson(cwd, state) {
1988
2054
  console.log(`FAIL opencode agent entries missing: ${missingAgents.join(", ")}`);
1989
2055
  }
1990
2056
  }
2057
+ const specialistRouting = inspectSpecialistRoutingConfig(opencodeConfig);
2058
+ if (specialistRouting.valid) {
2059
+ console.log(`PASS OpenCode specialist routing available (${CORE_AGENT_NAMES.length} callers, ${SPECIALIST_AGENT_NAMES.length} specialists)`);
2060
+ } else {
2061
+ state.hasWarning = true;
2062
+ for (const error of specialistRouting.errors) console.log(`WARN ${error}`);
2063
+ console.log("WARN specialist routing is stale or incomplete; rerun `ai-workflow init --yes --no-install`");
2064
+ }
2065
+ for (const warning of specialistRouting.warnings) {
2066
+ state.hasWarning = true;
2067
+ console.log(`WARN ${warning}`);
2068
+ }
1991
2069
  } catch {
1992
2070
  state.hasFailure = true;
1993
2071
  console.log("FAIL opencode.jsonc is not valid JSONC");