@event4u/agent-config 9.0.0 → 9.2.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 +22 -204
- package/MIGRATION.md +14 -0
- package/README.md +2 -2
- package/dist/agent-src/commands/explain-run.md +88 -0
- package/dist/agent-src/commands/implement-ticket.md +2 -0
- package/dist/agent-src/commands/pr/create/description-only.md +138 -8
- package/dist/agent-src/commands/pr/create.md +16 -5
- package/dist/agent-src/commands/review/changes.md +22 -0
- package/dist/agent-src/contexts/execution/subagent-modes-detail.md +41 -6
- package/dist/agent-src/contexts/execution/verify-budget.md +15 -0
- package/dist/agent-src/rules/cross-source-consistency.md +92 -0
- package/dist/agent-src/rules/delegation-policy.md +1 -1
- package/dist/agent-src/skills/estimate-ticket/SKILL.md +2 -0
- package/dist/agent-src/skills/feature-planning/SKILL.md +1 -1
- package/dist/agent-src/skills/llm-provider-knowledge/SKILL.md +146 -0
- package/dist/agent-src/skills/llm-provider-knowledge/evals/triggers.json +20 -0
- package/dist/agent-src/skills/po-discovery/SKILL.md +2 -0
- package/dist/agent-src/skills/refine-ticket/SKILL.md +7 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +14 -0
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +26 -5
- package/dist/agent-src/skills/subagent-orchestration/evals/evals.json +18 -0
- package/dist/agent-src/skills/subagent-orchestration/prompts/README.md +14 -2
- package/dist/agent-src/skills/subagent-orchestration/prompts/adversarial-verification-council.md +79 -0
- package/dist/agent-src/skills/subagent-orchestration/schemas/adversarial-findings.json +56 -0
- package/dist/agent-src/skills/token-optimizer/SKILL.md +10 -4
- package/dist/agent-src/templates/agent-settings.md +28 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +4 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +105 -28
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +5 -5
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +16 -12
- package/dist/discovery/trust-report.md +8 -8
- package/dist/discovery/workspaces.json +12 -8
- package/dist/install/install.mjs +47 -31
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +4 -1
- package/dist/scripts/_lib/agent_settings.js.map +1 -1
- package/dist/server/schemas/settings.js +10 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/CLAIMS.md +10 -3
- package/docs/MIGRATION.md +20 -0
- package/docs/architecture.md +4 -4
- package/docs/archive/CHANGELOG-pre-9.2.0.md +276 -0
- package/docs/benchmark.md +24 -0
- package/docs/command-flows.md +1 -1
- package/docs/contracts/agents-layout.md +1 -1
- package/docs/contracts/consumer-bridge.md +55 -63
- package/docs/contracts/install-layout.md +1 -1
- package/docs/contracts/migrate-command.md +1 -1
- package/docs/customization.md +22 -1
- package/docs/decisions/ADR-020-global-only-consumer-scope.md +34 -0
- package/docs/decisions/ADR-122-adversarial-verification-council.md +136 -0
- package/docs/decisions/INDEX.md +1 -0
- package/docs/design/adversarial-council-eval.md +106 -0
- package/docs/featured-skills.md +2 -2
- package/docs/getting-started-by-role.md +1 -1
- package/docs/getting-started.md +2 -2
- package/docs/governance-advantage.md +1 -1
- package/docs/guidelines/agent-infra/api-cost-levers.md +126 -0
- package/docs/guidelines/agent-infra/cross-source-consistency-mechanics.md +109 -0
- package/docs/installation.md +2 -1
- package/docs/maintainers/dev-mode.md +3 -2
- package/docs/proof.md +7 -7
- package/package.json +1 -1
- package/src/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +4 -1
- package/src/config/agent-settings.template.yml +81 -0
- package/src/config/gitignore-block.txt +16 -6
- package/src/scripts/_cli/cmd_conformance.ts +2 -2
- package/src/scripts/_cli/cmd_doctor.ts +162 -13
- package/src/scripts/_cli/cmd_refresh.ts +35 -118
- package/src/scripts/_lib/adversarial_council_gate.test.ts +112 -0
- package/src/scripts/_lib/adversarial_council_gate.ts +98 -0
- package/src/scripts/_lib/adversarial_reconcile.test.ts +197 -0
- package/src/scripts/_lib/adversarial_reconcile.ts +187 -0
- package/src/scripts/_lib/agent_settings.ts +4 -1
- package/src/scripts/_lib/verify_budget.ts +4 -1
- package/src/scripts/ai_council/clients.ts +68 -6
- package/src/scripts/ai_council/pricing.ts +62 -0
- package/src/scripts/council_cli.ts +19 -3
- package/src/scripts/install.ts +44 -45
- package/src/scripts/lint_agents_layout.ts +10 -4
- package/src/scripts/lint_breaking_changes_index.ts +303 -0
- package/src/scripts/mcp_server/tools.ts +1 -3
- package/src/scripts/schemas/agent-settings.schema.json +28 -0
- package/src/scripts/self_review_gate.test.ts +11 -0
- package/src/scripts/self_review_gate.ts +7 -1
- package/src/scripts/skill_eval_coverage.ts +4 -1
- package/src/server/schemas/settings.ts +24 -0
|
@@ -138,6 +138,12 @@ export class CouncilResponse {
|
|
|
138
138
|
text: string;
|
|
139
139
|
input_tokens: number;
|
|
140
140
|
output_tokens: number;
|
|
141
|
+
// Prompt-cache accounting (Anthropic). `cache_read_input_tokens` bill at
|
|
142
|
+
// ~0.1× input; `cache_creation_input_tokens` at 1.25×/2×. Both default 0
|
|
143
|
+
// (no cache, or a provider that does not report them) so existing callers
|
|
144
|
+
// and the persisted response JSON stay backward-compatible.
|
|
145
|
+
cache_creation_input_tokens: number;
|
|
146
|
+
cache_read_input_tokens: number;
|
|
141
147
|
latency_ms: number;
|
|
142
148
|
error: string | null;
|
|
143
149
|
metadata: Record<string, unknown>;
|
|
@@ -148,6 +154,8 @@ export class CouncilResponse {
|
|
|
148
154
|
text: string;
|
|
149
155
|
input_tokens?: number;
|
|
150
156
|
output_tokens?: number;
|
|
157
|
+
cache_creation_input_tokens?: number;
|
|
158
|
+
cache_read_input_tokens?: number;
|
|
151
159
|
latency_ms?: number;
|
|
152
160
|
error?: string | null;
|
|
153
161
|
metadata?: Record<string, unknown>;
|
|
@@ -157,6 +165,8 @@ export class CouncilResponse {
|
|
|
157
165
|
this.text = opts.text;
|
|
158
166
|
this.input_tokens = opts.input_tokens ?? 0;
|
|
159
167
|
this.output_tokens = opts.output_tokens ?? 0;
|
|
168
|
+
this.cache_creation_input_tokens = opts.cache_creation_input_tokens ?? 0;
|
|
169
|
+
this.cache_read_input_tokens = opts.cache_read_input_tokens ?? 0;
|
|
160
170
|
this.latency_ms = opts.latency_ms ?? 0;
|
|
161
171
|
this.error = opts.error ?? null;
|
|
162
172
|
// Python dataclass uses `field(default_factory=dict)` — each instance
|
|
@@ -278,6 +288,15 @@ interface ApiClientOptions {
|
|
|
278
288
|
model?: string | undefined;
|
|
279
289
|
client?: unknown;
|
|
280
290
|
api_key?: string | null | undefined;
|
|
291
|
+
// Anthropic prompt caching (GA — no beta header). **Explicit opt-in,
|
|
292
|
+
// default OFF** — a caller must pass `true` to send the stable `system` +
|
|
293
|
+
// artefact prefix as cache-controlled blocks (member 2..N in a round, and
|
|
294
|
+
// round-2+ within the 5-min TTL, then read it at ~0.1× input). The council
|
|
295
|
+
// builder opts in via `_construct_api_member`; any other `AnthropicClient`
|
|
296
|
+
// caller (e.g. an API-mode team, a single-shot feature) gets NO caching
|
|
297
|
+
// unless it asks — so a single call never pays the ~1.25× write premium by
|
|
298
|
+
// surprise. Only AnthropicClient consumes this; other providers ignore it.
|
|
299
|
+
enable_prompt_cache?: boolean | undefined;
|
|
281
300
|
}
|
|
282
301
|
|
|
283
302
|
/** Shared ctor-options shape for the CLI clients. */
|
|
@@ -335,10 +354,15 @@ export class AnthropicClient extends ExternalAIClient {
|
|
|
335
354
|
override name = 'anthropic';
|
|
336
355
|
override billable = true;
|
|
337
356
|
private _client: unknown;
|
|
357
|
+
// Prompt caching is explicit opt-in (default OFF) — see
|
|
358
|
+
// ApiClientOptions.enable_prompt_cache. Prevents silent activation (and the
|
|
359
|
+
// write premium) for callers that don't benefit from a shared-prefix cache.
|
|
360
|
+
private _enablePromptCache: boolean;
|
|
338
361
|
|
|
339
362
|
constructor(opts: ApiClientOptions = {}) {
|
|
340
363
|
super();
|
|
341
364
|
this.model = opts.model ?? DEFAULT_ANTHROPIC_MODEL;
|
|
365
|
+
this._enablePromptCache = opts.enable_prompt_cache ?? false;
|
|
342
366
|
if (opts.client !== undefined && opts.client !== null) {
|
|
343
367
|
this._client = opts.client;
|
|
344
368
|
return;
|
|
@@ -381,12 +405,44 @@ export class AnthropicClient extends ExternalAIClient {
|
|
|
381
405
|
if (typeof create !== 'function') {
|
|
382
406
|
throw new TypeError("'messages.create' is not callable");
|
|
383
407
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
408
|
+
// Prompt caching (GA — no beta header). Breakpoints on BOTH the
|
|
409
|
+
// system block (stable across rounds) and the user artefact block
|
|
410
|
+
// (the large system+artefact prefix is byte-identical across
|
|
411
|
+
// members 2..N within a round, so they read it at ~0.1× input).
|
|
412
|
+
// base_system_prompt alone is often below the model's min cacheable
|
|
413
|
+
// prefix and would never cache solo — the artefact block is where
|
|
414
|
+
// the real saving lands. Two breakpoints (≤ the 4 allowed).
|
|
415
|
+
const kwargs: Record<string, unknown> = this._enablePromptCache
|
|
416
|
+
? {
|
|
417
|
+
model: this.model,
|
|
418
|
+
max_tokens,
|
|
419
|
+
system: [
|
|
420
|
+
{
|
|
421
|
+
type: 'text',
|
|
422
|
+
text: system_prompt,
|
|
423
|
+
cache_control: { type: 'ephemeral' },
|
|
424
|
+
},
|
|
425
|
+
],
|
|
426
|
+
messages: [
|
|
427
|
+
{
|
|
428
|
+
role: 'user',
|
|
429
|
+
content: [
|
|
430
|
+
{
|
|
431
|
+
type: 'text',
|
|
432
|
+
text: user_prompt,
|
|
433
|
+
cache_control: { type: 'ephemeral' },
|
|
434
|
+
},
|
|
435
|
+
],
|
|
436
|
+
},
|
|
437
|
+
],
|
|
438
|
+
}
|
|
439
|
+
: {
|
|
440
|
+
model: this.model,
|
|
441
|
+
max_tokens,
|
|
442
|
+
system: system_prompt,
|
|
443
|
+
messages: [{ role: 'user', content: user_prompt }],
|
|
444
|
+
};
|
|
445
|
+
response = create.call(messages, kwargs);
|
|
390
446
|
} catch (exc) {
|
|
391
447
|
return new CouncilResponse({
|
|
392
448
|
provider: this.name,
|
|
@@ -426,6 +482,12 @@ export class AnthropicClient extends ExternalAIClient {
|
|
|
426
482
|
text,
|
|
427
483
|
input_tokens: usage ? (_getattr(usage, 'input_tokens', 0) as number) : 0,
|
|
428
484
|
output_tokens: usage ? (_getattr(usage, 'output_tokens', 0) as number) : 0,
|
|
485
|
+
cache_creation_input_tokens: usage
|
|
486
|
+
? (_getattr(usage, 'cache_creation_input_tokens', 0) as number)
|
|
487
|
+
: 0,
|
|
488
|
+
cache_read_input_tokens: usage
|
|
489
|
+
? (_getattr(usage, 'cache_read_input_tokens', 0) as number)
|
|
490
|
+
: 0,
|
|
429
491
|
latency_ms,
|
|
430
492
|
});
|
|
431
493
|
}
|
|
@@ -101,6 +101,68 @@ export function estimate_cost(
|
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
// ── prompt-cache repricing ─────────────────────────────────────────
|
|
105
|
+
//
|
|
106
|
+
// Prompt-cache multipliers derived from the base input rate (Anthropic, GA —
|
|
107
|
+
// no beta header): cache reads bill at ~0.1× input; cache writes at 1.25×
|
|
108
|
+
// (5-min TTL) / 2× (1-h TTL). Kept as CONSTANTS, not columns in
|
|
109
|
+
// `.agent-prices.md`, because that table's row format is byte-frozen (see the
|
|
110
|
+
// file header) and downstream tests pin it.
|
|
111
|
+
export const CACHE_READ_MULTIPLIER = 0.1;
|
|
112
|
+
export const CACHE_WRITE_MULTIPLIER_5M = 1.25;
|
|
113
|
+
export const CACHE_WRITE_MULTIPLIER_1H = 2.0;
|
|
114
|
+
|
|
115
|
+
export interface CacheTokenBreakdown {
|
|
116
|
+
input_tokens: number; // uncached input, billed at the full rate
|
|
117
|
+
cache_read_input_tokens?: number; // billed at CACHE_READ_MULTIPLIER × input
|
|
118
|
+
cache_creation_input_tokens?: number; // billed at the write multiplier
|
|
119
|
+
output_tokens: number;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Realized cost of a call given OBSERVED cache-token counts (from
|
|
124
|
+
* `CouncilResponse.usage`). Deliberately separate from `estimate_cost`, which
|
|
125
|
+
* stays cache-agnostic (a conservative 0%-cache-hit pre-flight) so the budget
|
|
126
|
+
* gate never under-predicts. This function is the post-hoc actual that surfaces
|
|
127
|
+
* the saving. `ttl` selects the write multiplier (default 5-min).
|
|
128
|
+
*/
|
|
129
|
+
export function reprice_with_cache(
|
|
130
|
+
provider: string,
|
|
131
|
+
model: string,
|
|
132
|
+
tokens: CacheTokenBreakdown,
|
|
133
|
+
table: PriceTable,
|
|
134
|
+
ttl: '5m' | '1h' = '5m',
|
|
135
|
+
): CostEstimate {
|
|
136
|
+
const price = lookup(table, provider, model);
|
|
137
|
+
const read = tokens.cache_read_input_tokens ?? 0;
|
|
138
|
+
const write = tokens.cache_creation_input_tokens ?? 0;
|
|
139
|
+
const totalInput = tokens.input_tokens + read + write;
|
|
140
|
+
if (price === null) {
|
|
141
|
+
return {
|
|
142
|
+
provider,
|
|
143
|
+
model,
|
|
144
|
+
input_tokens: totalInput,
|
|
145
|
+
output_tokens: tokens.output_tokens,
|
|
146
|
+
input_usd: 0.0,
|
|
147
|
+
output_usd: 0.0,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const writeMult = ttl === '1h' ? CACHE_WRITE_MULTIPLIER_1H : CACHE_WRITE_MULTIPLIER_5M;
|
|
151
|
+
const inputUsd =
|
|
152
|
+
(tokens.input_tokens / 1_000_000) * price.input_per_1m_usd +
|
|
153
|
+
(read / 1_000_000) * price.input_per_1m_usd * CACHE_READ_MULTIPLIER +
|
|
154
|
+
(write / 1_000_000) * price.input_per_1m_usd * writeMult;
|
|
155
|
+
const outputUsd = (tokens.output_tokens / 1_000_000) * price.output_per_1m_usd;
|
|
156
|
+
return {
|
|
157
|
+
provider,
|
|
158
|
+
model,
|
|
159
|
+
input_tokens: totalInput,
|
|
160
|
+
output_tokens: tokens.output_tokens,
|
|
161
|
+
input_usd: inputUsd,
|
|
162
|
+
output_usd: outputUsd,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
104
166
|
// ── staleness ──────────────────────────────────────────────────────
|
|
105
167
|
|
|
106
168
|
/** Return the most recent Monday 00:00 UTC as a date (YYYY-MM-DD). */
|
|
@@ -603,8 +603,13 @@ function build_members(settings: Dict, opts: BuildMembersOptions = {}): External
|
|
|
603
603
|
);
|
|
604
604
|
}
|
|
605
605
|
const api_key_ref = (cfg['api_key_ref'] as string | null) ?? null;
|
|
606
|
+
// Council opts in explicitly (caching is client-default OFF): on
|
|
607
|
+
// unless the operator sets `prompt_cache: false` on the member.
|
|
608
|
+
const enable_prompt_cache = cfg['prompt_cache'] !== false;
|
|
606
609
|
for (const sib_model of siblings[name] as string[]) {
|
|
607
|
-
members.push(
|
|
610
|
+
members.push(
|
|
611
|
+
_construct_api_member(name, sib_model, { api_key_ref, enable_prompt_cache }),
|
|
612
|
+
);
|
|
608
613
|
}
|
|
609
614
|
continue;
|
|
610
615
|
}
|
|
@@ -613,6 +618,9 @@ function build_members(settings: Dict, opts: BuildMembersOptions = {}): External
|
|
|
613
618
|
members.push(
|
|
614
619
|
_construct_api_member(name, model, {
|
|
615
620
|
api_key_ref: (cfg['api_key_ref'] as string | null) ?? null,
|
|
621
|
+
// Council opts in explicitly (client-default OFF); on unless
|
|
622
|
+
// the operator sets `prompt_cache: false`.
|
|
623
|
+
enable_prompt_cache: cfg['prompt_cache'] !== false,
|
|
616
624
|
}),
|
|
617
625
|
);
|
|
618
626
|
} else if (mode === 'cli' && _CLI_PROVIDERS.has(name)) {
|
|
@@ -747,14 +755,18 @@ function _format_advisor_summary(
|
|
|
747
755
|
function _construct_api_member(
|
|
748
756
|
name: string,
|
|
749
757
|
model: string | null,
|
|
750
|
-
opts: { api_key_ref?: string | null } = {},
|
|
758
|
+
opts: { api_key_ref?: string | null; enable_prompt_cache?: boolean | undefined } = {},
|
|
751
759
|
): ExternalAIClient {
|
|
752
760
|
const api_key_ref = opts.api_key_ref ?? null;
|
|
753
761
|
if (name === 'anthropic') {
|
|
754
762
|
const api_key = api_key_ref
|
|
755
763
|
? resolve_api_key(api_key_ref, 'ai_council.members.anthropic')
|
|
756
764
|
: load_anthropic_key();
|
|
757
|
-
return new AnthropicClient({
|
|
765
|
+
return new AnthropicClient({
|
|
766
|
+
model: model || 'claude-sonnet-4-5',
|
|
767
|
+
api_key,
|
|
768
|
+
enable_prompt_cache: opts.enable_prompt_cache,
|
|
769
|
+
});
|
|
758
770
|
}
|
|
759
771
|
if (name === 'openai') {
|
|
760
772
|
const api_key = api_key_ref
|
|
@@ -1409,6 +1421,8 @@ function _serialise_responses(responses: CouncilResponse[]): Dict[] {
|
|
|
1409
1421
|
text: r.text,
|
|
1410
1422
|
input_tokens: r.input_tokens,
|
|
1411
1423
|
output_tokens: r.output_tokens,
|
|
1424
|
+
cache_creation_input_tokens: r.cache_creation_input_tokens,
|
|
1425
|
+
cache_read_input_tokens: r.cache_read_input_tokens,
|
|
1412
1426
|
latency_ms: r.latency_ms,
|
|
1413
1427
|
error: r.error,
|
|
1414
1428
|
metadata,
|
|
@@ -1427,6 +1441,8 @@ function _deserialise_responses(items: Dict[]): CouncilResponse[] {
|
|
|
1427
1441
|
text: (d['text'] as string) ?? '',
|
|
1428
1442
|
input_tokens: _pyInt(d['input_tokens'] ?? 0, 0),
|
|
1429
1443
|
output_tokens: _pyInt(d['output_tokens'] ?? 0, 0),
|
|
1444
|
+
cache_creation_input_tokens: _pyInt(d['cache_creation_input_tokens'] ?? 0, 0),
|
|
1445
|
+
cache_read_input_tokens: _pyInt(d['cache_read_input_tokens'] ?? 0, 0),
|
|
1430
1446
|
latency_ms: _pyInt(d['latency_ms'] ?? 0, 0),
|
|
1431
1447
|
error: (d['error'] as string | null) ?? null,
|
|
1432
1448
|
metadata: (d['metadata'] as Dict) || {},
|
package/src/scripts/install.ts
CHANGED
|
@@ -2494,7 +2494,11 @@ function _detect_legacy_for_migration(project_root: string): string[] {
|
|
|
2494
2494
|
return [];
|
|
2495
2495
|
}
|
|
2496
2496
|
|
|
2497
|
-
|
|
2497
|
+
// Migration-idempotency sentinel. Previously the consumer bridge marker;
|
|
2498
|
+
// that file was retired (ADR-020 amendment 2026-07-13), so the durable
|
|
2499
|
+
// "this project has already been installed / migrated" signal is now the
|
|
2500
|
+
// install-mode marker, written on every full or minimal install.
|
|
2501
|
+
if (isFile(path.join(project_root, INSTALL_MODE_MARKER_REL))) return [];
|
|
2498
2502
|
|
|
2499
2503
|
const found: string[] = [];
|
|
2500
2504
|
for (const name of MIGRATE_LEGACY_YAML_FILES) {
|
|
@@ -2573,38 +2577,34 @@ function _format_global_root_for_marker(global_root: string): string {
|
|
|
2573
2577
|
}
|
|
2574
2578
|
|
|
2575
2579
|
/**
|
|
2576
|
-
*
|
|
2577
|
-
*
|
|
2578
|
-
*
|
|
2580
|
+
* Remove a legacy `agents/.event4u-bridge.yml` if a previous install wrote
|
|
2581
|
+
* one. The bridge marker was retired (ADR-020 dated amendment 2026-07-13):
|
|
2582
|
+
* it carried no project-specific data — `global_root` is derived directly
|
|
2583
|
+
* from the well-known `~/.event4u/agent-config` path — yet was committed to
|
|
2584
|
+
* the shared project tree, so every developer's install rewrote its volatile
|
|
2585
|
+
* `installed_at` / `installer_version` fields and overwrote each other's.
|
|
2586
|
+
* The global root is now resolved from the well-known path; the migration
|
|
2587
|
+
* idempotency sentinel moved to `agents/.agent-state/install-mode.txt`.
|
|
2588
|
+
*
|
|
2589
|
+
* Skipped under `AGENT_CONFIG_DEV_MODE=1` and inside the agent-config source
|
|
2590
|
+
* repo (detected by the uncondensed-source marker dir) — the same gate the
|
|
2591
|
+
* writer used. Returns the removed path (so the caller can report it) or null.
|
|
2579
2592
|
*/
|
|
2580
|
-
function
|
|
2593
|
+
function _remove_legacy_consumer_bridge_marker(
|
|
2581
2594
|
project_root: string,
|
|
2582
|
-
installer_version: string,
|
|
2583
2595
|
env: NodeJS.ProcessEnv | null = null,
|
|
2584
|
-
now: Date | null = null,
|
|
2585
2596
|
): string | null {
|
|
2586
2597
|
const env_map = env ?? process.env;
|
|
2587
2598
|
if (env_map['AGENT_CONFIG_DEV_MODE'] === '1') return null;
|
|
2588
2599
|
if (isDir(path.join(project_root, '.agent-src.uncondensed'))) return null;
|
|
2589
2600
|
|
|
2590
|
-
const global_root_str = _format_global_root_for_marker(
|
|
2591
|
-
user_global_paths.event4u_root(env_map),
|
|
2592
|
-
);
|
|
2593
|
-
const stamp = utcStamp(now ?? undefined);
|
|
2594
|
-
|
|
2595
|
-
const body =
|
|
2596
|
-
'# event4u/agent-config — consumer bridge marker (auto-written).\n' +
|
|
2597
|
-
'# Spec: docs/contracts/consumer-bridge.md (event4u-bridge/v1).\n' +
|
|
2598
|
-
'# Reader contract: expand ~ against the current $HOME; fail closed\n' +
|
|
2599
|
-
'# when global_root is missing on disk; never write back through it.\n' +
|
|
2600
|
-
'schema: event4u-bridge/v1\n' +
|
|
2601
|
-
`global_root: ${global_root_str}\n` +
|
|
2602
|
-
`installed_at: ${stamp}\n` +
|
|
2603
|
-
`installer_version: ${installer_version}\n`;
|
|
2604
|
-
|
|
2605
2601
|
const target = path.join(project_root, CONSUMER_BRIDGE_MARKER_RELPATH);
|
|
2606
|
-
|
|
2607
|
-
|
|
2602
|
+
if (!isFile(target)) return null;
|
|
2603
|
+
try {
|
|
2604
|
+
fs.rmSync(target);
|
|
2605
|
+
} catch {
|
|
2606
|
+
return null;
|
|
2607
|
+
}
|
|
2608
2608
|
return target;
|
|
2609
2609
|
}
|
|
2610
2610
|
|
|
@@ -2641,16 +2641,14 @@ function _write_per_tool_project_anchors(
|
|
|
2641
2641
|
const target = path.join(project_root, rel_path);
|
|
2642
2642
|
mkdirp(path.dirname(target));
|
|
2643
2643
|
|
|
2644
|
-
const bridge_abs = path.join(project_root, CONSUMER_BRIDGE_MARKER_RELPATH);
|
|
2645
|
-
const bridge_rel = path.relative(path.dirname(target), bridge_abs);
|
|
2646
|
-
|
|
2647
2644
|
const body =
|
|
2648
2645
|
'# event4u/agent-config — per-tool project anchor (auto-written).\n' +
|
|
2649
2646
|
'# Spec: docs/contracts/consumer-bridge.md § Per-tool anchor strategy.\n' +
|
|
2650
|
-
`# Tool: ${tool_id}.
|
|
2647
|
+
`# Tool: ${tool_id}. Resolves the global install directly — no\n` +
|
|
2648
|
+
'# project-local bridge marker (retired, ADR-020 amendment 2026-07-13).\n' +
|
|
2649
|
+
'# This anchor is gitignored: each developer regenerates it on install.\n' +
|
|
2651
2650
|
'schema: event4u-bridge/v1\n' +
|
|
2652
2651
|
`tool: ${tool_id}\n` +
|
|
2653
|
-
`bridge: ${bridge_rel}\n` +
|
|
2654
2652
|
`global_root: ${global_root_str}\n` +
|
|
2655
2653
|
`installed_at: ${stamp}\n`;
|
|
2656
2654
|
|
|
@@ -3377,15 +3375,15 @@ function install_global(
|
|
|
3377
3375
|
const rc = _update_installed_tools_manifest(project_root, tools, 'global', force, files_by_tool);
|
|
3378
3376
|
if (rc !== 0) return rc;
|
|
3379
3377
|
|
|
3380
|
-
// Consumer bridge marker (
|
|
3381
|
-
//
|
|
3382
|
-
//
|
|
3383
|
-
const
|
|
3384
|
-
if (
|
|
3385
|
-
const rel = isRelativeTo(
|
|
3386
|
-
? path.relative(project_root,
|
|
3387
|
-
:
|
|
3388
|
-
info(`
|
|
3378
|
+
// Consumer bridge marker retired (ADR-020 amendment 2026-07-13):
|
|
3379
|
+
// clean up any legacy `agents/.event4u-bridge.yml` a prior install
|
|
3380
|
+
// committed. The global root is resolved from the well-known path.
|
|
3381
|
+
const removed_marker = _remove_legacy_consumer_bridge_marker(project_root);
|
|
3382
|
+
if (removed_marker !== null && !state.QUIET) {
|
|
3383
|
+
const rel = isRelativeTo(removed_marker, project_root)
|
|
3384
|
+
? path.relative(project_root, removed_marker)
|
|
3385
|
+
: removed_marker;
|
|
3386
|
+
info(`Removed legacy bridge marker: ${rel}`);
|
|
3389
3387
|
}
|
|
3390
3388
|
|
|
3391
3389
|
const anchor_paths = _write_per_tool_project_anchors(project_root, tools);
|
|
@@ -3800,13 +3798,14 @@ function install_minimal(target_root_in: string, force: boolean, user_type: stri
|
|
|
3800
3798
|
}
|
|
3801
3799
|
}
|
|
3802
3800
|
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3801
|
+
// Bridge marker retired (ADR-020 amendment 2026-07-13) — clean up any
|
|
3802
|
+
// legacy `agents/.event4u-bridge.yml` a prior install left behind.
|
|
3803
|
+
const removed_marker = _remove_legacy_consumer_bridge_marker(target_root);
|
|
3804
|
+
if (removed_marker !== null) {
|
|
3805
|
+
const rel = isRelativeTo(removed_marker, target_root)
|
|
3806
|
+
? path.relative(target_root, removed_marker)
|
|
3807
|
+
: removed_marker;
|
|
3808
|
+
success(`Removed legacy bridge marker: ${rel}`);
|
|
3810
3809
|
}
|
|
3811
3810
|
|
|
3812
3811
|
_write_install_mode_marker(target_root, 'minimal');
|
|
@@ -42,7 +42,11 @@ const ALLOWED_FLAT_FILES: ReadonlySet<string> = new Set([
|
|
|
42
42
|
// separate violation caught by check_tracked_but_ignored.ts.
|
|
43
43
|
'.ai-video.xml', // operator AI-video config; gitignored per .gitignore § AI Video
|
|
44
44
|
'installed-tools.lock', // installer per-user inventory; gitignored per ADR-020
|
|
45
|
-
|
|
45
|
+
// Retired marker (ADR-020 amendment 2026-07-13). No longer written; the
|
|
46
|
+
// global root resolves from `~/.event4u/agent-config`. Kept tolerated so a
|
|
47
|
+
// legacy leftover in a not-yet-reinstalled consumer is not flagged as a
|
|
48
|
+
// violation — the next install deletes it.
|
|
49
|
+
'.event4u-bridge.yml',
|
|
46
50
|
]);
|
|
47
51
|
|
|
48
52
|
// All directories allowed at the agents/ root in the SOURCE REPO.
|
|
@@ -75,6 +79,8 @@ const ALLOWED_SOURCE_DIRS: ReadonlySet<string> = new Set([
|
|
|
75
79
|
const CONSUMER_EXPECTED_ENTRIES: ReadonlySet<string> = new Set([
|
|
76
80
|
// Required consumer entries
|
|
77
81
|
'overrides',
|
|
82
|
+
// Retired marker (ADR-020 amendment 2026-07-13) — no longer written, but
|
|
83
|
+
// kept in the tolerated set so a legacy leftover does not warn.
|
|
78
84
|
'.event4u-bridge.yml',
|
|
79
85
|
'.gitkeep',
|
|
80
86
|
// Optional but legitimate consumer entries (will not trigger warnings)
|
|
@@ -93,9 +99,9 @@ const CONSUMER_EXPECTED_ENTRIES: ReadonlySet<string> = new Set([
|
|
|
93
99
|
const MIGRATE_HINT =
|
|
94
100
|
'Run `npx @event4u/agent-config migrate` to sweep legacy project-scope ' +
|
|
95
101
|
'artefacts in one pass. The unified `migrate` command (see ' +
|
|
96
|
-
'`docs/contracts/migrate-command.md`) leaves `agents/overrides/`
|
|
97
|
-
'
|
|
98
|
-
'
|
|
102
|
+
'`docs/contracts/migrate-command.md`) leaves `agents/overrides/` as the ' +
|
|
103
|
+
'only consumer-side agent surface; the wizard recreates fresh config on ' +
|
|
104
|
+
'`agent-config setup`.';
|
|
99
105
|
|
|
100
106
|
function _isDir(p: string): boolean {
|
|
101
107
|
try {
|