@event4u/agent-config 9.3.0 → 9.4.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 (72) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +78 -0
  3. package/CONTRIBUTING.md +14 -0
  4. package/README.md +12 -4
  5. package/dist/agent-src/commands/contribution-precheck.md +100 -0
  6. package/dist/agent-src/commands/team.md +4 -0
  7. package/dist/agent-src/contexts/model-recommendations.md +6 -0
  8. package/dist/agent-src/rules/token-efficiency.md +7 -0
  9. package/dist/agent-src/skills/adversarial-review/SKILL.md +5 -0
  10. package/dist/agent-src/skills/ai-council/SKILL.md +3 -0
  11. package/dist/agent-src/skills/build-buy-partner/SKILL.md +1 -0
  12. package/dist/agent-src/skills/decision-record/SKILL.md +35 -1
  13. package/dist/agent-src/skills/decision-record/evals/triggers.json +82 -12
  14. package/dist/agent-src/skills/decision-record/references/weighted-matrix.md +119 -0
  15. package/dist/agent-src/skills/evaluate-llm-feature/SKILL.md +142 -0
  16. package/dist/agent-src/skills/judge-synthesis/SKILL.md +1 -0
  17. package/dist/agent-src/skills/learning-tutor/SKILL.md +215 -0
  18. package/dist/agent-src/skills/rice-prioritization/SKILL.md +1 -0
  19. package/dist/agent-src/skills/stakeholder-tradeoff/SKILL.md +3 -1
  20. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +2 -1
  21. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  22. package/dist/discovery/deprecation-report.md +1 -1
  23. package/dist/discovery/discovery-manifest.json +94 -24
  24. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  25. package/dist/discovery/discovery-manifest.summary.md +4 -4
  26. package/dist/discovery/orphan-report.md +1 -1
  27. package/dist/discovery/packs.json +13 -10
  28. package/dist/discovery/trust-report.md +3 -3
  29. package/dist/discovery/workspaces.json +9 -6
  30. package/dist/mcp/registry-manifest.json +3 -3
  31. package/docs/CLAIMS.md +16 -2
  32. package/docs/announcements/2026-07-honest-cost-sweep-launch.md +84 -0
  33. package/docs/anti-reskin-gate.md +60 -0
  34. package/docs/architecture.md +2 -2
  35. package/docs/catalog.md +303 -278
  36. package/docs/command-flows.md +2 -1
  37. package/docs/comparison.yaml +10 -0
  38. package/docs/contracts/adoption-signal-floor.md +17 -0
  39. package/docs/contracts/ai-council-config.md +25 -0
  40. package/docs/contracts/mcp-tool-inventory.md +25 -22
  41. package/docs/contracts/tier-3-contrib-plugin.md +12 -4
  42. package/docs/featured-skills.md +2 -2
  43. package/docs/getting-started-by-role.md +1 -1
  44. package/docs/getting-started.md +1 -1
  45. package/docs/governance-advantage.md +1 -1
  46. package/docs/proof.md +10 -6
  47. package/docs/self-review-gate.md +14 -0
  48. package/docs/setup/per-ide/antigravity.md +8 -3
  49. package/docs/us-vs-the-category.md +38 -0
  50. package/docs/wedge/production-validator/README.md +24 -0
  51. package/docs/wedge/production-validator/first-run-check.sh +42 -0
  52. package/package.json +1 -1
  53. package/src/scripts/_lib/catalog_score.ts +64 -0
  54. package/src/scripts/_lib/shingle_similarity.ts +146 -0
  55. package/src/scripts/ai_council/clients.ts +50 -1
  56. package/src/scripts/ai_council/config.ts +28 -3
  57. package/src/scripts/ai_council/low_impact.ts +16 -0
  58. package/src/scripts/ai_council/necessity.ts +11 -0
  59. package/src/scripts/ai_council/orchestrator.ts +78 -26
  60. package/src/scripts/ai_council/pricing.ts +59 -0
  61. package/src/scripts/ai_council/stance_tally.ts +42 -5
  62. package/src/scripts/build_catalog_index.ts +188 -0
  63. package/src/scripts/council_cli.ts +56 -1
  64. package/src/scripts/generate_index.ts +6 -3
  65. package/src/scripts/lint_originality.ts +464 -0
  66. package/src/scripts/mcp_server/catalog_tools.ts +114 -0
  67. package/src/scripts/mcp_server/consumer_tool_catalog.json +46 -0
  68. package/src/scripts/schemas/flow.schema.json +17 -0
  69. package/src/scripts/schemas/skill.schema.json +166 -32
  70. package/src/scripts/self_review_gate.test.ts +48 -0
  71. package/src/scripts/self_review_gate.ts +73 -5
  72. package/src/scripts/validate_flow_teams.ts +127 -0
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Build the lazy-catalog index — `dist/catalog-index-v1.json`.
4
+ *
5
+ * A queryable metadata index over every skill, persona, and domain command:
6
+ * `{ id, cls, name, description, tags, path }` — **metadata only, no bodies**.
7
+ * Bodies load lazily from disk at `catalog_load` time (the stdio server has the
8
+ * repo underneath it, unlike the upstream Hermes plugin which embeds bodies).
9
+ *
10
+ * Feeds the `catalog_search / _inspect / _load` MCP tools (built as discovery
11
+ * stubs; activation deferred behind a quality A/B — see
12
+ * road-to-persona-library-harvest.md Phase 2). Deterministic output (entries
13
+ * sorted by (cls, id), stable keys) so the artifact diffs cleanly. `--check`
14
+ * fails on drift instead of writing (CI mode), mirroring the
15
+ * build_mcp_registry_manifest convention.
16
+ */
17
+ import * as fs from 'node:fs';
18
+ import * as path from 'node:path';
19
+ import { fileURLToPath, pathToFileURL } from 'node:url';
20
+
21
+ import { parse as parseYaml } from 'yaml';
22
+
23
+ const _HERE = fileURLToPath(import.meta.url);
24
+ export const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
25
+ // The `-v1` namespace tracks `schema_version` below (per lint_versioned_cache):
26
+ // bump both together on a shape change so any stale cached copy self-invalidates
27
+ // by name rather than being read as the new shape.
28
+ export const OUT = path.join(ROOT, 'dist', 'catalog-index-v1.json');
29
+
30
+ export type CatalogClass = 'skill' | 'persona' | 'command';
31
+
32
+ export interface CatalogIndexEntry {
33
+ /** Globally unique handle: `<cls>:<slug>`. */
34
+ id: string;
35
+ cls: CatalogClass;
36
+ name: string;
37
+ description: string;
38
+ /** Domain / packs / tier — for filtering and search-text weighting. */
39
+ tags: string[];
40
+ /** Repo-relative path to the body (resolved by catalog_load). */
41
+ path: string;
42
+ }
43
+
44
+ const SKILLS_DIR = path.join(ROOT, 'src', 'skills');
45
+ const PERSONAS_DIR = path.join(ROOT, 'src', 'agent-src', 'personas');
46
+ const DOMAINS_DIR = path.join(ROOT, 'src', 'domains');
47
+
48
+ function _isDir(p: string): boolean {
49
+ try { return fs.statSync(p).isDirectory(); } catch { return false; }
50
+ }
51
+ function _isFile(p: string): boolean {
52
+ try { return fs.statSync(p).isFile(); } catch { return false; }
53
+ }
54
+ function _rel(abs: string): string {
55
+ return path.relative(ROOT, abs).split(path.sep).join('/');
56
+ }
57
+
58
+ function _frontmatter(abs: string): Record<string, unknown> {
59
+ const text = fs.readFileSync(abs, 'utf-8');
60
+ const m = /^---\n([\s\S]*?)\n---/.exec(text);
61
+ if (!m || m.index !== 0) return {};
62
+ try {
63
+ const fm = parseYaml(m[1] as string, { version: '1.1' });
64
+ return fm !== null && typeof fm === 'object' && !Array.isArray(fm) ? (fm as Record<string, unknown>) : {};
65
+ } catch {
66
+ return {};
67
+ }
68
+ }
69
+
70
+ function _str(v: unknown): string {
71
+ return typeof v === 'string' ? v.trim() : v == null ? '' : String(v);
72
+ }
73
+ function _list(v: unknown): string[] {
74
+ if (Array.isArray(v)) return v.map((x) => _str(x)).filter((s) => s.length > 0);
75
+ const s = _str(v);
76
+ return s ? [s] : [];
77
+ }
78
+
79
+ function _walkLeaf(dir: string, leaf: string, out: string[]): void {
80
+ let entries: fs.Dirent[];
81
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
82
+ for (const ent of entries) {
83
+ const full = path.join(dir, ent.name);
84
+ if (ent.isDirectory() || (ent.isSymbolicLink() && _isDir(full))) _walkLeaf(full, leaf, out);
85
+ else if (ent.name === leaf) out.push(full);
86
+ }
87
+ }
88
+
89
+ export function buildEntries(): CatalogIndexEntry[] {
90
+ const out: CatalogIndexEntry[] = [];
91
+
92
+ // Skills
93
+ if (_isDir(SKILLS_DIR)) {
94
+ for (const slug of fs.readdirSync(SKILLS_DIR).sort()) {
95
+ const abs = path.join(SKILLS_DIR, slug, 'SKILL.md');
96
+ if (!_isFile(abs)) continue;
97
+ const fm = _frontmatter(abs);
98
+ out.push({
99
+ id: `skill:${slug}`,
100
+ cls: 'skill',
101
+ name: _str(fm['name']) || slug,
102
+ description: _str(fm['description']),
103
+ tags: [_str(fm['domain']), ..._list(fm['packs'])].filter((s) => s.length > 0),
104
+ path: _rel(abs),
105
+ });
106
+ }
107
+ }
108
+
109
+ // Personas (top-level *.md, skip _template/advisors and underscore-prefixed)
110
+ if (_isDir(PERSONAS_DIR)) {
111
+ for (const name of fs.readdirSync(PERSONAS_DIR).sort()) {
112
+ if (!name.endsWith('.md') || name.startsWith('_')) continue;
113
+ const abs = path.join(PERSONAS_DIR, name);
114
+ if (!_isFile(abs)) continue;
115
+ const fm = _frontmatter(abs);
116
+ const id = _str(fm['id']) || name.replace(/\.md$/, '');
117
+ out.push({
118
+ id: `persona:${id}`,
119
+ cls: 'persona',
120
+ name: _str(fm['role']) || id,
121
+ description: _str(fm['description']),
122
+ tags: [_str(fm['tier']), _str(fm['mode'])].filter((s) => s.length > 0),
123
+ path: _rel(abs),
124
+ });
125
+ }
126
+ }
127
+
128
+ // Domain commands
129
+ const cmds: string[] = [];
130
+ _walkLeaf(DOMAINS_DIR, 'command.md', cmds);
131
+ for (const abs of cmds.sort()) {
132
+ const fm = _frontmatter(abs);
133
+ // logical id = <pack>/<subpath> under src/domains, without /command.md
134
+ const relFromDomains = path.relative(DOMAINS_DIR, path.dirname(abs)).split(path.sep).join('/');
135
+ const name = _str(fm['name']) || relFromDomains.split('/').pop() || relFromDomains;
136
+ out.push({
137
+ id: `command:${relFromDomains}`,
138
+ cls: 'command',
139
+ name,
140
+ description: _str(fm['description']) || _str(fm['intent']),
141
+ tags: [_str(fm['pack']), _str(fm['tier']), _str(fm['visibility'])].filter((s) => s.length > 0),
142
+ path: _rel(abs),
143
+ });
144
+ }
145
+
146
+ // Deterministic order: (cls, id).
147
+ out.sort((a, b) => (a.cls < b.cls ? -1 : a.cls > b.cls ? 1 : a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
148
+ return out;
149
+ }
150
+
151
+ export function serialize(entries: CatalogIndexEntry[]): string {
152
+ return JSON.stringify({ schema_version: 1, count: entries.length, entries }, null, 2) + '\n';
153
+ }
154
+
155
+ export function main(argv?: string[]): number {
156
+ const args = argv ?? process.argv.slice(2);
157
+ const check = args.includes('--check');
158
+ const quiet = args.includes('--quiet');
159
+ const entries = buildEntries();
160
+ const rendered = serialize(entries);
161
+
162
+ if (check) {
163
+ const current = _isFile(OUT) ? fs.readFileSync(OUT, 'utf-8') : '';
164
+ if (current !== rendered) {
165
+ process.stderr.write(`build_catalog_index: DRIFT — ${_rel(OUT)} is stale; run build_catalog_index to regenerate.\n`);
166
+ return 1;
167
+ }
168
+ if (!quiet) process.stdout.write(`build_catalog_index: OK — ${entries.length} entries, index up to date.\n`);
169
+ return 0;
170
+ }
171
+
172
+ fs.mkdirSync(path.dirname(OUT), { recursive: true });
173
+ fs.writeFileSync(OUT, rendered, 'utf-8');
174
+ if (!quiet) process.stdout.write(`build_catalog_index: wrote ${entries.length} entries → ${_rel(OUT)}\n`);
175
+ return 0;
176
+ }
177
+
178
+ function _isCliEntry(): boolean {
179
+ if (process.argv[1] === undefined) return false;
180
+ if (import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) return true;
181
+ try {
182
+ return fs.realpathSync(fileURLToPath(import.meta.url)) === fs.realpathSync(path.resolve(process.argv[1]));
183
+ } catch { return false; }
184
+ }
185
+
186
+ if (_isCliEntry()) {
187
+ process.exit(main());
188
+ }
@@ -98,7 +98,9 @@ import {
98
98
  } from './ai_council/orchestrator.js';
99
99
  import {
100
100
  type PriceTable,
101
+ downgrade_coupling,
101
102
  estimate_cost,
103
+ estimate_input_tokens,
102
104
  load_prices,
103
105
  prices_file_for,
104
106
  } from './ai_council/pricing.js';
@@ -1605,7 +1607,8 @@ function _necessity_gate(opts: {
1605
1607
  function _resolve_model_downgrade(ai_cfg: Dict, lens: string): [boolean, boolean] {
1606
1608
  const md_block = (ai_cfg['model_downgrade'] as Dict) || {};
1607
1609
  let enabled = md_block['enabled'] === undefined ? true : Boolean(_pyBool(md_block['enabled']));
1608
- let auto_apply = md_block['auto_apply'] === undefined ? false : Boolean(_pyBool(md_block['auto_apply']));
1610
+ // A3: auto-downgrade is the default; `auto_apply: false` is the opt-out.
1611
+ let auto_apply = md_block['auto_apply'] === undefined ? true : Boolean(_pyBool(md_block['auto_apply']));
1609
1612
  const overrides = ((ai_cfg['lens_overrides'] as Dict) || {})['model_downgrade'] || {};
1610
1613
  const lens_override = _isDict(overrides) ? (overrides as Dict)[lens] : null;
1611
1614
  if (_isDict(lens_override)) {
@@ -1622,6 +1625,10 @@ function _size_fit_gate(opts: {
1622
1625
  members: ExternalAIClient[];
1623
1626
  ai_cfg: Dict;
1624
1627
  stdout?: ((s: string) => void) | null;
1628
+ /** Planned rounds for the run; expected same-model cache reads = rounds − 1. */
1629
+ rounds?: number;
1630
+ /** Price table for the A1↔A3 cache-coupling gate; null → coupling skipped. */
1631
+ price_table?: PriceTable | null;
1625
1632
  }): Array<[string, SizeFitVerdict, boolean]> {
1626
1633
  const out = opts.stdout ?? _stdout;
1627
1634
  const [enabled, auto_apply] = _resolve_model_downgrade(opts.ai_cfg, opts.lens);
@@ -1629,8 +1636,25 @@ function _size_fit_gate(opts: {
1629
1636
  if (!enabled) {
1630
1637
  return decisions;
1631
1638
  }
1639
+ const md_block = (opts.ai_cfg['model_downgrade'] as Dict) || {};
1640
+ const tier_override = (md_block['model_tier_override'] as Dict) || {};
1641
+ const cache_enabled = opts.ai_cfg['prompt_cache'] !== false;
1642
+ const expected_reads = Math.max(0, (opts.rounds ?? 1) - 1);
1632
1643
  const members_cfg = (opts.ai_cfg['members'] as Dict) || {};
1633
1644
  for (const member of opts.members) {
1645
+ // Per-run escape hatch: a member pinned in model_tier_override gets
1646
+ // exactly that model — classifier and coupling both skipped.
1647
+ const pinned = tier_override[member.name];
1648
+ if (typeof pinned === 'string' && pinned.length > 0) {
1649
+ if (pinned !== member.model) {
1650
+ out(
1651
+ `council:size-fit · ${member.name} · model_tier_override ` +
1652
+ `\`${member.model}\` → \`${pinned}\` (operator pin)\n`,
1653
+ );
1654
+ member.model = pinned;
1655
+ }
1656
+ continue;
1657
+ }
1634
1658
  const member_cfg = (members_cfg[member.name] as Dict) || {};
1635
1659
  const ladder = (member_cfg['model_ladder'] as string[]) || [];
1636
1660
  if (ladder.length === 0) {
@@ -1639,6 +1663,35 @@ function _size_fit_gate(opts: {
1639
1663
  const verdict = classify_size_fit(opts.prompt, member.model, ladder, opts.lens);
1640
1664
  let applied = false;
1641
1665
  if (!verdict.fit && verdict.suggested_model) {
1666
+ // A1↔A3 coupling: a downgraded member misses the model-scoped
1667
+ // prompt cache — downgrade only when the model saving beats the
1668
+ // forfeited cache reads. No price table → conservative skip is
1669
+ // wrong for the cost goal, so coupling only gates when computable.
1670
+ if (opts.price_table) {
1671
+ const prefix_tokens = estimate_input_tokens(opts.prompt);
1672
+ const coupling = downgrade_coupling(
1673
+ member.name,
1674
+ member.model,
1675
+ verdict.suggested_model,
1676
+ prefix_tokens,
1677
+ 4096,
1678
+ {
1679
+ enabled: cache_enabled,
1680
+ cacheable_prefix_tokens: prefix_tokens,
1681
+ expected_reads,
1682
+ },
1683
+ opts.price_table,
1684
+ );
1685
+ if (!coupling.net_positive) {
1686
+ out(
1687
+ `council:size-fit · ${member.name} · downgrade skipped — ` +
1688
+ `lost cache savings $${coupling.lost_cache_savings_usd.toFixed(4)} ` +
1689
+ `≥ model savings $${coupling.downgrade_savings_usd.toFixed(4)}\n`,
1690
+ );
1691
+ decisions.push([member.name, verdict, false]);
1692
+ continue;
1693
+ }
1694
+ }
1642
1695
  if (auto_apply) {
1643
1696
  out(
1644
1697
  `council:size-fit · ${member.name} · auto-downgrade ` +
@@ -1845,6 +1898,8 @@ function cmd_run(
1845
1898
  lens: question.mode,
1846
1899
  members,
1847
1900
  ai_cfg,
1901
+ rounds: _getattr(args, 'rounds', 1) as number,
1902
+ price_table: table,
1848
1903
  });
1849
1904
  const project = detect_project_context(REPO_ROOT);
1850
1905
  const billable = members.filter((m) => _getattr(m, 'billable', true));
@@ -232,7 +232,10 @@ export function _collect_skills(): Entry[] {
232
232
  kind: 'skill',
233
233
  name,
234
234
  description: _truncate(fm['description'] ?? ''),
235
- extra: '',
235
+ // Provenance: `official` unless the skill declares
236
+ // `provenance: community` (external contribution — credited in
237
+ // CREDITS.md; the catalog column makes the credit visible).
238
+ extra: fm['provenance'] ?? 'official',
236
239
  path: _relativeToPosix(skillMd, ROOT),
237
240
  });
238
241
  }
@@ -372,7 +375,7 @@ export function _render_index(
372
375
  '',
373
376
  `## Skills (${skills.length})`,
374
377
  '',
375
- _render_table(skills, ['kind', 'name', 'extra', 'description'], '../'),
378
+ _render_table(skills, ['kind', 'name', 'source', 'description'], '../'),
376
379
  '',
377
380
  `## Rules (${rules.length})`,
378
381
  '',
@@ -428,7 +431,7 @@ export function _render_catalog(
428
431
  '> (`./scripts-run src/scripts/domain_soundness_status`). A disclaimer',
429
432
  '> floor bounds liability, not correctness — see [the proof page](proof.md).',
430
433
  '',
431
- _render_table(skills, ['kind', 'name', 'extra', 'description'], '../', _to_shipped_path),
434
+ _render_table(skills, ['kind', 'name', 'source', 'description'], '../', _to_shipped_path),
432
435
  '',
433
436
  `## Rules (${publicRules.length})`,
434
437
  '',