@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +78 -0
- package/CONTRIBUTING.md +14 -0
- package/README.md +12 -4
- package/dist/agent-src/commands/contribution-precheck.md +100 -0
- package/dist/agent-src/commands/team.md +4 -0
- package/dist/agent-src/contexts/model-recommendations.md +6 -0
- package/dist/agent-src/rules/token-efficiency.md +7 -0
- package/dist/agent-src/skills/adversarial-review/SKILL.md +5 -0
- package/dist/agent-src/skills/ai-council/SKILL.md +3 -0
- package/dist/agent-src/skills/build-buy-partner/SKILL.md +1 -0
- package/dist/agent-src/skills/decision-record/SKILL.md +35 -1
- package/dist/agent-src/skills/decision-record/evals/triggers.json +82 -12
- package/dist/agent-src/skills/decision-record/references/weighted-matrix.md +119 -0
- package/dist/agent-src/skills/evaluate-llm-feature/SKILL.md +142 -0
- package/dist/agent-src/skills/judge-synthesis/SKILL.md +1 -0
- package/dist/agent-src/skills/learning-tutor/SKILL.md +215 -0
- package/dist/agent-src/skills/rice-prioritization/SKILL.md +1 -0
- package/dist/agent-src/skills/stakeholder-tradeoff/SKILL.md +3 -1
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +2 -1
- 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 +94 -24
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -4
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +13 -10
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +9 -6
- package/dist/mcp/registry-manifest.json +3 -3
- package/docs/CLAIMS.md +16 -2
- package/docs/announcements/2026-07-honest-cost-sweep-launch.md +84 -0
- package/docs/anti-reskin-gate.md +60 -0
- package/docs/architecture.md +2 -2
- package/docs/catalog.md +303 -278
- package/docs/command-flows.md +2 -1
- package/docs/comparison.yaml +10 -0
- package/docs/contracts/adoption-signal-floor.md +17 -0
- package/docs/contracts/ai-council-config.md +25 -0
- package/docs/contracts/mcp-tool-inventory.md +25 -22
- package/docs/contracts/tier-3-contrib-plugin.md +12 -4
- package/docs/featured-skills.md +2 -2
- package/docs/getting-started-by-role.md +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/governance-advantage.md +1 -1
- package/docs/proof.md +10 -6
- package/docs/self-review-gate.md +14 -0
- package/docs/setup/per-ide/antigravity.md +8 -3
- package/docs/us-vs-the-category.md +38 -0
- package/docs/wedge/production-validator/README.md +24 -0
- package/docs/wedge/production-validator/first-run-check.sh +42 -0
- package/package.json +1 -1
- package/src/scripts/_lib/catalog_score.ts +64 -0
- package/src/scripts/_lib/shingle_similarity.ts +146 -0
- package/src/scripts/ai_council/clients.ts +50 -1
- package/src/scripts/ai_council/config.ts +28 -3
- package/src/scripts/ai_council/low_impact.ts +16 -0
- package/src/scripts/ai_council/necessity.ts +11 -0
- package/src/scripts/ai_council/orchestrator.ts +78 -26
- package/src/scripts/ai_council/pricing.ts +59 -0
- package/src/scripts/ai_council/stance_tally.ts +42 -5
- package/src/scripts/build_catalog_index.ts +188 -0
- package/src/scripts/council_cli.ts +56 -1
- package/src/scripts/generate_index.ts +6 -3
- package/src/scripts/lint_originality.ts +464 -0
- package/src/scripts/mcp_server/catalog_tools.ts +114 -0
- package/src/scripts/mcp_server/consumer_tool_catalog.json +46 -0
- package/src/scripts/schemas/flow.schema.json +17 -0
- package/src/scripts/schemas/skill.schema.json +166 -32
- package/src/scripts/self_review_gate.test.ts +48 -0
- package/src/scripts/self_review_gate.ts +73 -5
- 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
|
-
|
|
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
|
-
|
|
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', '
|
|
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', '
|
|
434
|
+
_render_table(skills, ['kind', 'name', 'source', 'description'], '../', _to_shipped_path),
|
|
432
435
|
'',
|
|
433
436
|
`## Rules (${publicRules.length})`,
|
|
434
437
|
'',
|