* feat(#1355): detect-and-warn guard for claude-code agent-teams GSD's multi-agent orchestration can stall under claude-code's experimental agent-teams (a subagent's completion fails to route to the orchestrator). Per the maintainer decision, the accepted scope is a read-only detector + one non-fatal warning — NOT the declined run_in_background/TaskOutput conversion. - New Teams Status Module (src/teams-status.cts → gsd-core/bin/lib/teams-status.cjs): pure resolveTeamsStatus({runtime, env}) + thin CLI cmdTeamsStatus reusing resolveRuntime. active = strictly-truthy env flag AND runtime === 'claude'. - Wire `gsd-tools query teams-status [--active]` (read-only; no capability registration needed — conformance gates govern features, not query commands). - One non-fatal warning in plan-phase.md before the first Agent spawn, gated on `query teams-status --active`; zero behavior change on non-claude/teams-off. - Hermeticity: clear CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS in run-tests.cjs + SESSION_ENV_KEYS. Docs reference + CONTEXT.md glossary. Built lib gitignored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(#1355): add changeset for teams-detect guard Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(#1355): bump plan-phase.md workflow size baseline (+407B for teams warning) The non-fatal agent-teams warning block added to plan-phase.md grew it 92759 → 93166 bytes, past its committed per-file baseline ratchet. The growth is small, deliberate, and still well under the workflow tier hard cap. Regenerate the baseline via `npm run size:baseline` (only plan-phase.md changed). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(#1355): register teams-status.cjs in the inventory manifest The new teams-status CLI module is a tracked surface; regenerate docs/INVENTORY-MANIFEST.json (cli_modules family) via gen-inventory-manifest.cjs --write so the inventory-manifest-sync gate passes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
93
src/teams-status.cts
Normal file
93
src/teams-status.cts
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Teams Status Module — issue #1355
|
||||
*
|
||||
* Read-only detector for claude-code's experimental agent-teams feature.
|
||||
* Exposes a PURE core function (env injected, no process.env/disk inside) and
|
||||
* a thin CLI wrapper that reuses resolveRuntime from runtime-slash.cjs.
|
||||
*
|
||||
* Exports:
|
||||
* resolveTeamsStatus({ runtime, env }) → TeamsStatus
|
||||
* cmdTeamsStatus(cwd, opts) — I/O entry point
|
||||
*
|
||||
* resolveTeamsStatus is PURE: env and runtime are injected, no process.env or
|
||||
* disk access inside the function. Pass process.env explicitly at call sites.
|
||||
*
|
||||
* cmdTeamsStatus is the I/O handler. It reads process.env, resolves the
|
||||
* runtime via resolveRuntime(cwd) from runtime-slash.cjs (GSD_RUNTIME →
|
||||
* config.runtime → 'claude' precedence), then:
|
||||
* - default: prints JSON.stringify(status) to stdout via io.output, exits 0.
|
||||
* - --active: prints nothing, exits 0 if status.active, exit 1 otherwise.
|
||||
*
|
||||
* Strictly read-only — no config writes, no disk mutation.
|
||||
*
|
||||
* Dependencies:
|
||||
* - ./io.cjs (output)
|
||||
* - ./runtime-slash.cjs (resolveRuntime)
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import ioMod = require('./io.cjs');
|
||||
const { output: coreOutput } = ioMod;
|
||||
|
||||
// ─── Types ────────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface TeamsStatus {
|
||||
/** true only when env flag is strictly-truthy AND runtime === 'claude' */
|
||||
active: boolean;
|
||||
/** resolved runtime name */
|
||||
runtime: string;
|
||||
/** true when CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS is set to a truthy value */
|
||||
env_present: boolean;
|
||||
/** human-readable source description */
|
||||
source: 'on: env' | 'off: flag absent' | 'off: non-claude';
|
||||
}
|
||||
|
||||
// ─── Pure core ────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Resolve the agent-teams status from injected runtime and env.
|
||||
*
|
||||
* Strict truthiness: only '1' and 'true' (case-insensitive, trimmed) are on.
|
||||
* '0', 'false', '', and unset are all off.
|
||||
*
|
||||
* @param opts.runtime The resolved runtime name (e.g. 'claude', 'codex')
|
||||
* @param opts.env The environment map to read from (typically process.env)
|
||||
*/
|
||||
export function resolveTeamsStatus(opts: { runtime: string; env: NodeJS.ProcessEnv }): TeamsStatus {
|
||||
const raw = (opts.env['CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS'] ?? '').trim().toLowerCase();
|
||||
const envOn = raw === '1' || raw === 'true'; // strict: never '0'/'false'/'' as on
|
||||
const isClaude = opts.runtime === 'claude';
|
||||
const source = !isClaude ? 'off: non-claude' : (envOn ? 'on: env' : 'off: flag absent');
|
||||
return { active: envOn && isClaude, runtime: opts.runtime, env_present: envOn, source };
|
||||
}
|
||||
|
||||
// ─── CLI command handler ──────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Command entry point: resolve runtime via resolveRuntime(cwd), read process.env,
|
||||
* call resolveTeamsStatus, and emit the result.
|
||||
*
|
||||
* @param cwd Project root directory (used by resolveRuntime for config.json)
|
||||
* @param opts Command options
|
||||
* @param opts.active When true: print nothing, exit 0 if active, exit 1 otherwise
|
||||
*/
|
||||
export function cmdTeamsStatus(cwd: string, opts: { active?: boolean }): void {
|
||||
// Resolve runtime via the canonical precedence:
|
||||
// GSD_RUNTIME → config.runtime → 'claude'
|
||||
// Reuses resolveRuntime from runtime-slash.cjs — no reimplementation.
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const runtimeSlash = require('./runtime-slash.cjs') as {
|
||||
resolveRuntime: (projectDir: string | null | undefined) => string;
|
||||
};
|
||||
const runtime = runtimeSlash.resolveRuntime(cwd);
|
||||
|
||||
const status = resolveTeamsStatus({ runtime, env: process.env });
|
||||
|
||||
if (opts.active) {
|
||||
// --active mode: no output, exit code encodes the boolean
|
||||
process.exit(status.active ? 0 : 1);
|
||||
}
|
||||
|
||||
// Default: emit JSON to stdout via io.output, exit 0
|
||||
coreOutput(status, false);
|
||||
}
|
||||
Reference in New Issue
Block a user