@event4u/agent-config 8.12.0 → 8.13.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.
Files changed (42) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +28 -0
  3. package/README.md +1 -1
  4. package/dist/agent-src/commands/pr/create.md +26 -0
  5. package/dist/agent-src/commands/team/adversarial.md +96 -0
  6. package/dist/agent-src/commands/team/delegate.md +109 -0
  7. package/dist/agent-src/commands/team/review.md +84 -0
  8. package/dist/agent-src/commands/team/status.md +92 -0
  9. package/dist/agent-src/commands/team.md +147 -0
  10. package/dist/agent-src/rules/improve-before-implement.md +2 -0
  11. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  12. package/dist/discovery/deprecation-report.md +1 -1
  13. package/dist/discovery/discovery-manifest.json +148 -10
  14. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  15. package/dist/discovery/discovery-manifest.summary.md +3 -3
  16. package/dist/discovery/orphan-report.md +1 -1
  17. package/dist/discovery/packs.json +11 -6
  18. package/dist/discovery/trust-report.md +2 -2
  19. package/dist/discovery/workspaces.json +10 -5
  20. package/dist/install/install.mjs +38 -0
  21. package/dist/mcp/registry-manifest.json +2 -2
  22. package/dist/server/schemas/settings.js +7 -0
  23. package/dist/server/schemas/settings.js.map +1 -1
  24. package/docs/CLAIMS.md +1 -1
  25. package/docs/architecture.md +1 -1
  26. package/docs/catalog.md +10 -4
  27. package/docs/command-flows.md +6 -1
  28. package/docs/contracts/ai-team-config.md +191 -0
  29. package/docs/contracts/command-clusters.md +1 -0
  30. package/docs/featured-skills.md +1 -1
  31. package/docs/getting-started-by-role.md +3 -1
  32. package/docs/getting-started.md +31 -1
  33. package/docs/maintainers/branch-protection.md +18 -0
  34. package/docs/proof.md +1 -1
  35. package/package.json +1 -1
  36. package/src/config/agent-settings.template.yml +40 -0
  37. package/src/scripts/_cli/cmd_doctor.ts +196 -1
  38. package/src/scripts/_lib/claude_plugin.ts +39 -4
  39. package/src/scripts/ai_team/config.ts +162 -0
  40. package/src/scripts/install.ts +39 -0
  41. package/src/scripts/rule_backlinks.ts +0 -0
  42. package/src/server/schemas/settings.ts +17 -0
@@ -27,6 +27,22 @@ export const CLAUDE_PLUGIN_INSTALL_HINT: readonly string[] = [
27
27
  `claude plugin install ${CLAUDE_PLUGIN_ID}@${CLAUDE_MARKETPLACE_NAME}`,
28
28
  ];
29
29
 
30
+ /**
31
+ * Official OpenAI codex plugin for Claude Code (team mode's Claude-Code
32
+ * transport). Marketplace name verified against the upstream repo's
33
+ * `.claude-plugin/marketplace.json` (`name: "openai-codex"`, plugin
34
+ * `name: "codex"`) at HEAD 2026-07-12.
35
+ */
36
+ export const CODEX_PLUGIN_ID = 'codex';
37
+ export const CODEX_MARKETPLACE_NAME = 'openai-codex';
38
+ export const CODEX_MARKETPLACE_REPO = 'openai/codex-plugin-cc';
39
+
40
+ /** The one-time install commands surfaced when the codex plugin is missing. */
41
+ export const CODEX_PLUGIN_INSTALL_HINT: readonly string[] = [
42
+ `claude plugin marketplace add ${CODEX_MARKETPLACE_REPO}`,
43
+ `claude plugin install ${CODEX_PLUGIN_ID}@${CODEX_MARKETPLACE_NAME}`,
44
+ ];
45
+
30
46
  /** Claude Code config dir — `CLAUDE_CONFIG_DIR` env override, else `~/.claude`. */
31
47
  export function claude_config_dir(): string {
32
48
  const env = (process.env['CLAUDE_CONFIG_DIR'] ?? '').trim();
@@ -34,11 +50,11 @@ export function claude_config_dir(): string {
34
50
  }
35
51
 
36
52
  /**
37
- * True when the agent-config plugin is recorded in Claude Code's
53
+ * True when a plugin with the given id is recorded in Claude Code's
38
54
  * `plugins/installed_plugins.json` (any marketplace — matched by the
39
- * `agent-config@` id prefix). Unreadable / absent file → false.
55
+ * `<id>@` key prefix). Unreadable / absent file → false. Read-only.
40
56
  */
41
- export function claude_plugin_installed(): boolean {
57
+ function _plugin_id_recorded(plugin_id: string): boolean {
42
58
  const p = path.join(claude_config_dir(), 'plugins', 'installed_plugins.json');
43
59
  let data: unknown;
44
60
  try {
@@ -50,10 +66,29 @@ export function claude_plugin_installed(): boolean {
50
66
  const plugins = (data as { plugins?: unknown }).plugins;
51
67
  if (plugins === null || plugins === undefined || typeof plugins !== 'object') return false;
52
68
  return Object.keys(plugins as Record<string, unknown>).some((k) =>
53
- k.startsWith(`${CLAUDE_PLUGIN_ID}@`),
69
+ k.startsWith(`${plugin_id}@`),
54
70
  );
55
71
  }
56
72
 
73
+ /**
74
+ * True when the agent-config plugin is recorded in Claude Code's
75
+ * `plugins/installed_plugins.json` (any marketplace — matched by the
76
+ * `agent-config@` id prefix). Unreadable / absent file → false.
77
+ */
78
+ export function claude_plugin_installed(): boolean {
79
+ return _plugin_id_recorded(CLAUDE_PLUGIN_ID);
80
+ }
81
+
82
+ /**
83
+ * True when the official OpenAI codex plugin is recorded in Claude Code's
84
+ * `plugins/installed_plugins.json` (any marketplace — matched by the
85
+ * `codex@` id prefix). Unreadable / absent file → false. Read-only:
86
+ * detection never writes under `~/.claude/`.
87
+ */
88
+ export function codex_plugin_installed(): boolean {
89
+ return _plugin_id_recorded(CODEX_PLUGIN_ID);
90
+ }
91
+
57
92
  /**
58
93
  * Version recorded in the installed marketplace snapshot's
59
94
  * `.claude-plugin/marketplace.json` (`metadata.version` — bumped per
@@ -0,0 +1,162 @@
1
+ /**
2
+ * ai_team configuration loader — single source of truth for the `/team`
3
+ * cross-model review family.
4
+ *
5
+ * Mirrors the fail-closed validation posture of the council loader
6
+ * (`src/scripts/ai_council/config.ts`) at a fraction of its surface: the
7
+ * `ai_team` block has five flat keys and no nested sub-schemas, so this
8
+ * loader is deliberately lean — defaults, per-key type checks, and hard
9
+ * unknown-key rejection. Contract: `docs/contracts/ai-team-config.md`.
10
+ *
11
+ * Unlike the council (ALWAYS user-global `.ai-council.yml`, ADR-104),
12
+ * `ai_team` lives in the PROJECT settings cascade (`.agent-settings.yml`
13
+ * via `load_agent_settings`) — team mode is a per-project posture
14
+ * (default-off), not a per-developer credential store; the codex CLI
15
+ * carries the subscription auth itself.
16
+ *
17
+ * Validation contract (all enforced at load time, fail-closed):
18
+ *
19
+ * 1. `ai_team` absent → defaults (feature off). Non-mapping → error.
20
+ * 2. Unknown keys under `ai_team` are rejected — a typo must never
21
+ * silently disable a gate (`allow_delegate` misspelled = delegation
22
+ * stays off AND the load fails loudly).
23
+ * 3. `enabled`, `allow_delegate`, `suppress_setup_hint` are booleans.
24
+ * 4. `model` is a non-empty string. `'auto'` = pass no `--model` flag
25
+ * (the codex CLI default applies); any other value passes through
26
+ * verbatim.
27
+ * 5. `max_calls_per_day` is a non-negative integer (bools rejected).
28
+ */
29
+ import { load_agent_settings, type SettingsDict } from '../_lib/agent_settings.js';
30
+
31
+ /** Raised when the `ai_team` block of `.agent-settings.yml` violates the schema. */
32
+ export class TeamConfigError extends Error {
33
+ constructor(message: string) {
34
+ super(message);
35
+ this.name = 'TeamConfigError';
36
+ }
37
+ }
38
+
39
+ export interface AiTeamConfig {
40
+ readonly enabled: boolean;
41
+ readonly model: string;
42
+ readonly allow_delegate: boolean;
43
+ readonly max_calls_per_day: number;
44
+ readonly suppress_setup_hint: boolean;
45
+ }
46
+
47
+ /** Shipped defaults — byte-parity with `src/config/agent-settings.template.yml`. */
48
+ export const AI_TEAM_DEFAULTS: AiTeamConfig = Object.freeze({
49
+ enabled: false,
50
+ model: 'auto',
51
+ allow_delegate: false,
52
+ max_calls_per_day: 50,
53
+ suppress_setup_hint: false,
54
+ });
55
+
56
+ /**
57
+ * Sentinel: `model: 'auto'` means "pass NO --model flag" so the codex
58
+ * CLI's own default applies (tracks the subscription's strongest model
59
+ * instead of pinning a stale ID).
60
+ */
61
+ export const AI_TEAM_MODEL_AUTO = 'auto';
62
+
63
+ const _KNOWN_KEYS: ReadonlySet<string> = new Set([
64
+ 'enabled',
65
+ 'model',
66
+ 'allow_delegate',
67
+ 'max_calls_per_day',
68
+ 'suppress_setup_hint',
69
+ ]);
70
+
71
+ function _isDict(value: unknown): value is Record<string, unknown> {
72
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
73
+ }
74
+
75
+ function _isBool(value: unknown): value is boolean {
76
+ return typeof value === 'boolean';
77
+ }
78
+
79
+ function _requireBool(raw: Record<string, unknown>, key: string, fallback: boolean): boolean {
80
+ if (!(key in raw)) {
81
+ return fallback;
82
+ }
83
+ const value = raw[key];
84
+ if (!_isBool(value)) {
85
+ throw new TeamConfigError(
86
+ `\`ai_team.${key}\` must be a boolean (got ${JSON.stringify(value)}).`,
87
+ );
88
+ }
89
+ return value;
90
+ }
91
+
92
+ /**
93
+ * Validate a raw `ai_team` mapping and return the typed config.
94
+ *
95
+ * `raw = null | undefined` (block absent) returns the defaults — the
96
+ * feature is off, never an error. Every other shape is validated
97
+ * fail-closed per the module contract above.
98
+ */
99
+ export function build_ai_team_config(raw: unknown): AiTeamConfig {
100
+ if (raw === null || raw === undefined) {
101
+ return AI_TEAM_DEFAULTS;
102
+ }
103
+ if (!_isDict(raw)) {
104
+ throw new TeamConfigError('`ai_team` must be a mapping.');
105
+ }
106
+
107
+ for (const key of Object.keys(raw)) {
108
+ if (!_KNOWN_KEYS.has(key)) {
109
+ throw new TeamConfigError(
110
+ `ai_team.${key}: unknown key; valid: ` +
111
+ `[${[..._KNOWN_KEYS].map((k) => `'${k}'`).join(', ')}].`,
112
+ );
113
+ }
114
+ }
115
+
116
+ const enabled = _requireBool(raw, 'enabled', AI_TEAM_DEFAULTS.enabled);
117
+ const allow_delegate = _requireBool(raw, 'allow_delegate', AI_TEAM_DEFAULTS.allow_delegate);
118
+ const suppress_setup_hint = _requireBool(
119
+ raw,
120
+ 'suppress_setup_hint',
121
+ AI_TEAM_DEFAULTS.suppress_setup_hint,
122
+ );
123
+
124
+ let model = AI_TEAM_DEFAULTS.model;
125
+ if ('model' in raw) {
126
+ const value = raw['model'];
127
+ if (typeof value !== 'string' || value.trim() === '') {
128
+ throw new TeamConfigError(
129
+ `\`ai_team.model\` must be a non-empty string (got ${JSON.stringify(value)}).`,
130
+ );
131
+ }
132
+ model = value;
133
+ }
134
+
135
+ let max_calls_per_day = AI_TEAM_DEFAULTS.max_calls_per_day;
136
+ if ('max_calls_per_day' in raw) {
137
+ const value = raw['max_calls_per_day'];
138
+ if (_isBool(value) || typeof value !== 'number' || !Number.isInteger(value) || value < 0) {
139
+ throw new TeamConfigError(
140
+ '`ai_team.max_calls_per_day` must be a non-negative integer ' +
141
+ `(got ${JSON.stringify(value)}).`,
142
+ );
143
+ }
144
+ max_calls_per_day = value;
145
+ }
146
+
147
+ return { enabled, model, allow_delegate, max_calls_per_day, suppress_setup_hint };
148
+ }
149
+
150
+ /**
151
+ * Load the `ai_team` config from the merged project settings cascade.
152
+ *
153
+ * `opts.settings` injects a pre-merged settings dict (test seam — no
154
+ * filesystem access); otherwise `load_agent_settings` walks the normal
155
+ * `.agent-settings.yml` cascade from `opts.cwd` (default: process cwd).
156
+ */
157
+ export function load_ai_team_config(
158
+ opts: { settings?: SettingsDict | null; cwd?: string | null } = {},
159
+ ): AiTeamConfig {
160
+ const settings = opts.settings ?? load_agent_settings({ cwd: opts.cwd ?? process.cwd() });
161
+ return build_ai_team_config((settings as Record<string, unknown>)['ai_team']);
162
+ }
@@ -4453,6 +4453,36 @@ function _read_consumer_auto_switch(project_root: string): string {
4453
4453
  return 'suggest';
4454
4454
  }
4455
4455
 
4456
+ /**
4457
+ * Team-mode setup hint (road-to-team-mode Phase 1 Step 2) — a one-line
4458
+ * recommendation printed after a claude-code install/init pointing at the
4459
+ * doctor `team` section. Returns null when suppressed via
4460
+ * `ai_team.suppress_setup_hint: true` in `.agent-settings.yml` (default
4461
+ * false; the `ai_team` block may not exist in the schema yet — read
4462
+ * leniently). Pointer only: never auto-installs, never modifies
4463
+ * `~/.claude/plugins`.
4464
+ */
4465
+ function _team_setup_hint_line(project_root: string): string | null {
4466
+ let data: Record<string, unknown>;
4467
+ try {
4468
+ data = load_agent_settings({ project_path: _resolve_settings_read(project_root) });
4469
+ } catch {
4470
+ data = {};
4471
+ }
4472
+ const ai_team = _isPlainObject(data) ? data['ai_team'] : null;
4473
+ const flag = _isPlainObject(ai_team)
4474
+ ? (ai_team as Record<string, unknown>)['suppress_setup_hint']
4475
+ : null;
4476
+ const suppressed =
4477
+ flag === true ||
4478
+ (typeof flag === 'string' && ['true', 'yes', 'on', '1'].includes(flag.trim().toLowerCase()));
4479
+ if (suppressed) return null;
4480
+ return (
4481
+ ' • Claude Code team mode (optional cross-model review via the official ' +
4482
+ 'codex plugin): run `agent-config doctor --check team` for setup status.'
4483
+ );
4484
+ }
4485
+
4456
4486
  function finalize_claude_model_tiers(project_root: string): number {
4457
4487
  const claude_skills = path.join(project_root, '.claude', 'skills');
4458
4488
  const augment_skills = path.join(project_root, '.augment', 'skills');
@@ -4644,6 +4674,13 @@ function _main_project_install(
4644
4674
  );
4645
4675
  process.stdout.write('\n');
4646
4676
  }
4677
+ if (_is_tool_enabled(tools, 'claude-code')) {
4678
+ const team_hint = _team_setup_hint_line(project_root);
4679
+ if (team_hint !== null) {
4680
+ process.stdout.write(team_hint + '\n');
4681
+ process.stdout.write('\n');
4682
+ }
4683
+ }
4647
4684
  }
4648
4685
 
4649
4686
  _propose_modules_config(project_root, is_first_run);
@@ -4730,6 +4767,8 @@ export {
4730
4767
  // ADR-200 py2ts: re-exported for test_consumer_model_tier.ts twin
4731
4768
  // (Python test imports `install.finalize_claude_model_tiers` directly).
4732
4769
  finalize_claude_model_tiers,
4770
+ // road-to-team-mode Phase 1 Step 2: exported for the wizard-hint tests.
4771
+ _team_setup_hint_line,
4733
4772
  SUPPORTED_PROFILES,
4734
4773
  DEFAULT_PROFILE,
4735
4774
  _VALID_TOOLS,
Binary file
@@ -288,6 +288,23 @@ export const settingsSchema = z.object({
288
288
  'When the agent considers a parallel `git worktree` for risky / large work. ask (default) = surface a numbered option and wait. on = spawn worktrees autonomously. off = never use worktrees, edit in place.',
289
289
  ),
290
290
  }),
291
+ ai_team: z.object({
292
+ enabled: z.boolean().default(false).describe(
293
+ 'Master switch for the /team cross-model review family (docs/contracts/ai-team-config.md). false (default) = commands are never suggested and every invocation refuses with an enable pointer. true = the family is live; individual gates (allow_delegate) still apply.',
294
+ ),
295
+ model: z.string().default('auto').describe(
296
+ "Model handed to the codex CLI. 'auto' (default) = pass no --model flag so the CLI's own default applies — tracks the subscription's current strongest model instead of pinning a stale ID. Any other value passes through verbatim as `--model <value>`.",
297
+ ),
298
+ allow_delegate: z.boolean().default(false).describe(
299
+ 'Second opt-in for the only wrapper that delegates write access (/team:delegate). false (default) = delegate refuses even when ai_team.enabled is true. Both keys must be true before delegation is reachable.',
300
+ ),
301
+ max_calls_per_day: z.number().int().min(0).default(50).describe(
302
+ 'Per-day cap on team calls, read against the EXISTING cli_call_budget openai bucket (~/.event4u/agent-config/cli-calls.json, daily UTC reset) — one subscription, one counter, never a parallel count. 0 = block all team calls.',
303
+ ),
304
+ suppress_setup_hint: z.boolean().default(false).describe(
305
+ 'Suppress the one-line wizard/init recommendation to set up the codex plugin on Claude-Code hosts. Cosmetic only — never changes behavior.',
306
+ ),
307
+ }).default({ enabled: false, model: 'auto', allow_delegate: false, max_calls_per_day: 50, suppress_setup_hint: false }),
291
308
  onboarding: z.object({
292
309
  onboarded: z.boolean().default(false).describe(
293
310
  'Set to true once the developer has completed `agent-config setup`. The onboarding-gate rule blocks the first turn of every chat until this is true. Toggle back to false to re-trigger the wizard.',