@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +28 -0
- package/README.md +1 -1
- package/dist/agent-src/commands/pr/create.md +26 -0
- package/dist/agent-src/commands/team/adversarial.md +96 -0
- package/dist/agent-src/commands/team/delegate.md +109 -0
- package/dist/agent-src/commands/team/review.md +84 -0
- package/dist/agent-src/commands/team/status.md +92 -0
- package/dist/agent-src/commands/team.md +147 -0
- package/dist/agent-src/rules/improve-before-implement.md +2 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +148 -10
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +11 -6
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +10 -5
- package/dist/install/install.mjs +38 -0
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/server/schemas/settings.js +7 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/CLAIMS.md +1 -1
- package/docs/architecture.md +1 -1
- package/docs/catalog.md +10 -4
- package/docs/command-flows.md +6 -1
- package/docs/contracts/ai-team-config.md +191 -0
- package/docs/contracts/command-clusters.md +1 -0
- package/docs/featured-skills.md +1 -1
- package/docs/getting-started-by-role.md +3 -1
- package/docs/getting-started.md +31 -1
- package/docs/maintainers/branch-protection.md +18 -0
- package/docs/proof.md +1 -1
- package/package.json +1 -1
- package/src/config/agent-settings.template.yml +40 -0
- package/src/scripts/_cli/cmd_doctor.ts +196 -1
- package/src/scripts/_lib/claude_plugin.ts +39 -4
- package/src/scripts/ai_team/config.ts +162 -0
- package/src/scripts/install.ts +39 -0
- package/src/scripts/rule_backlinks.ts +0 -0
- 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
|
|
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
|
-
*
|
|
55
|
+
* `<id>@` key prefix). Unreadable / absent file → false. Read-only.
|
|
40
56
|
*/
|
|
41
|
-
|
|
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(`${
|
|
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
|
+
}
|
package/src/scripts/install.ts
CHANGED
|
@@ -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.',
|