@event4u/agent-config 14.16.0 → 14.17.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 (38) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +83 -210
  5. package/README.md +1 -1
  6. package/dist/agent-src/commands/analyze/inbox.md +172 -6
  7. package/dist/agent-src/commands/fix/commit-messages.md +336 -0
  8. package/dist/agent-src/commands/fix.md +5 -3
  9. package/dist/agent-src/rules/commit-conventions.md +1 -1
  10. package/dist/agent-src/skills/aws-infrastructure/SKILL.md +41 -0
  11. package/dist/agent-src/skills/conventional-commits-writing/SKILL.md +234 -4
  12. package/dist/agent-src/skills/terraform/SKILL.md +29 -0
  13. package/dist/agent-src/skills/threat-modeling/data/reasoning.csv +1 -0
  14. package/dist/agent-src/skills/threat-modeling/data/surfaces.csv +1 -0
  15. package/dist/agent-src/skills/threat-modeling/data/threats.csv +5 -0
  16. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  17. package/dist/cli-delegate/cmd_explain.js +24 -112
  18. package/dist/discovery/deprecation-report.md +1 -1
  19. package/dist/discovery/discovery-manifest.json +42 -16
  20. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  21. package/dist/discovery/discovery-manifest.summary.md +3 -3
  22. package/dist/discovery/dormancy-report.md +1 -1
  23. package/dist/discovery/orphan-report.md +1 -1
  24. package/dist/discovery/packs.json +6 -5
  25. package/dist/discovery/trust-report.md +2 -2
  26. package/dist/discovery/workspaces.json +5 -4
  27. package/dist/mcp/registry-manifest.json +3 -3
  28. package/dist/mcp/server.json +2 -2
  29. package/package.json +1 -1
  30. package/src/config/web-launch-readiness.json +1 -1
  31. package/src/scripts/_cli/explain_last/index.ts +1 -1
  32. package/src/scripts/_cli/explain_last/memory.ts +8 -104
  33. package/src/scripts/check_artefact_count_messaging.ts +9 -0
  34. package/src/scripts/check_release_highlights.ts +77 -4
  35. package/src/scripts/check_web_launch_readiness.ts +295 -15
  36. package/src/scripts/measure_release_mix.ts +384 -0
  37. package/src/scripts/release_mix_taxonomy.json +172 -0
  38. package/src/scripts/attest_artifact.ts +0 -193
@@ -0,0 +1,384 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Release governance-vs-product mix — measured from changed paths, published as
4
+ * two views, with no threshold.
5
+ *
6
+ * Why this exists, and what it deliberately is not.
7
+ *
8
+ * An external reviewer asked twice for a per-PR rule: "no meta/governance
9
+ * feature without a user-facing artifact in the same PR". The AI council
10
+ * DECLINED that proposal (2026-09-04, anthropic/claude-sonnet-4-5 +
11
+ * openai/codex-default, 2 rounds, quorum 2/2, $0.00 — both seats
12
+ * subscription-authed) on two grounds: a same-PR gate measures packaging rather
13
+ * than progress, since one token consumer-file edit legitimises an otherwise
14
+ * governance-only pull request; and it rejects legitimate work by construction,
15
+ * since a CI fix, a dependency bump and an analysis round producing only
16
+ * roadmaps are all real work with no consumer surface. The decline is recorded
17
+ * in `docs/decisions/ADR-253-per-pr-user-artifact-gate-declined.md`.
18
+ *
19
+ * This script is the replacement both seats specified: classify at RELEASE
20
+ * level, publish two views, attach a response obligation, and refuse to pick a
21
+ * threshold until at least two readings exist. It therefore **enforces
22
+ * nothing about the ratio**. It measures and it publishes. The one enforced
23
+ * consequence lives in `check_release_highlights.ts` and is about the
24
+ * completeness of the written response, never about the number.
25
+ *
26
+ * The two views.
27
+ *
28
+ * - **Commit view** — exclusive counts per category, plus `mixed`, plus
29
+ * `unclassified`. A commit is classified from the files it touches.
30
+ * - **Diff view** — added/deleted lines per category over the whole range.
31
+ *
32
+ * Generated projections and lockfiles (`dist/`, `.augment/`, `.claude/`,
33
+ * `package-lock.json`, …) are removed from BOTH views by name. In this
34
+ * repository `dist/` is a byte-exact projection of `src/`, so leaving it in
35
+ * would let a mechanical regeneration dominate every reading. A commit left
36
+ * with no path after that exclusion lands in the `generated_only` diagnostic
37
+ * rather than in maintenance — otherwise the ratio would depend on whether the
38
+ * regeneration was committed separately from the source edit that caused it.
39
+ *
40
+ * Classification reads paths, never subjects.
41
+ *
42
+ * Commit subjects are mutable, inconsistently formatted, and can contradict the
43
+ * files a commit touches. Nothing in this file reads `%s`, `%b` or any other
44
+ * message field; the only git output consumed is a name list and a numstat.
45
+ * `tests/scripts/measure_release_mix.test.ts` pins that by rewriting a
46
+ * subject with `git commit --amend` and asserting the reading is byte-identical.
47
+ *
48
+ * `mixed` is never collapsed into a category.
49
+ *
50
+ * Collapsing `mixed` into product would recreate the exact gaming the declined
51
+ * rule permits. A commit touching two classified categories is `mixed` and is
52
+ * reported as its own bucket in both views.
53
+ *
54
+ * `unclassified` is separate from `mixed` and does not cause it. A commit whose
55
+ * paths are all unmatched is `unclassified`; a commit with one classified
56
+ * category plus unmatched paths keeps that category and carries
57
+ * `has_unclassified`. Conflating taxonomy uncertainty with genuine
58
+ * cross-category work would hide both.
59
+ *
60
+ * Usage.
61
+ *
62
+ * measure_release_mix --from <ref> --to <ref> [--label <tag>] [--json <path>] [--quiet]
63
+ * measure_release_mix --audit # every top-level tracked path resolves
64
+ *
65
+ * Exit codes: 0 clean · 1 an unresolved path in `--audit` · 2 usage or git error.
66
+ */
67
+ import { spawnSync } from 'node:child_process';
68
+ import * as fs from 'node:fs';
69
+ import * as path from 'node:path';
70
+ import process from 'node:process';
71
+ import { fileURLToPath, pathToFileURL } from 'node:url';
72
+
73
+ const _HERE = path.resolve(fileURLToPath(import.meta.url));
74
+ export const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
75
+ const TAXONOMY_PATH = path.join(_HERE, '..', 'release_mix_taxonomy.json');
76
+
77
+ export type Category = 'consumer' | 'governance' | 'maintenance' | 'unclassified';
78
+ export const CLASSIFIED: readonly Category[] = ['consumer', 'governance', 'maintenance'];
79
+
80
+ export interface Taxonomy {
81
+ taxonomy_version: string;
82
+ excluded_generated: string[];
83
+ rules: { prefix: string; category: Category }[];
84
+ governance_contracts: string[];
85
+ }
86
+
87
+ export function loadTaxonomy(file = TAXONOMY_PATH): Taxonomy {
88
+ const raw = JSON.parse(fs.readFileSync(file, 'utf8')) as Taxonomy;
89
+ if (!raw.taxonomy_version) throw new Error('taxonomy carries no taxonomy_version');
90
+ return raw;
91
+ }
92
+
93
+ /** True when the path is a generated projection or a lockfile — dropped from both views. */
94
+ export function isExcluded(p: string, tax: Taxonomy): boolean {
95
+ return tax.excluded_generated.some((pre) => (pre.endsWith('/') ? p.startsWith(pre) : p === pre));
96
+ }
97
+
98
+ /**
99
+ * Longest-prefix-wins. A path matching no rule is `unclassified` — a reported
100
+ * state, never a silent default into some category.
101
+ */
102
+ export function classifyPath(p: string, tax: Taxonomy): Category {
103
+ return classifyPathDetailed(p, tax).category;
104
+ }
105
+
106
+ /**
107
+ * The same decision, with the rule that made it. `matched: false` is the
108
+ * failure `--audit` looks for: a path that fell through every rule, which is
109
+ * not the same thing as a path the taxonomy deliberately calls unclassified.
110
+ */
111
+ export function classifyPathDetailed(
112
+ p: string,
113
+ tax: Taxonomy,
114
+ ): { category: Category; matched: boolean; rule: string } {
115
+ if (tax.governance_contracts.includes(p)) {
116
+ return { category: 'governance', matched: true, rule: `governance_contracts:${p}` };
117
+ }
118
+ let best: { len: number; category: Category; rule: string } | null = null;
119
+ for (const rule of tax.rules) {
120
+ if (!p.startsWith(rule.prefix)) continue;
121
+ if (best === null || rule.prefix.length > best.len) {
122
+ best = { len: rule.prefix.length, category: rule.category, rule: rule.prefix };
123
+ }
124
+ }
125
+ return best === null
126
+ ? { category: 'unclassified', matched: false, rule: '' }
127
+ : { category: best.category, matched: true, rule: best.rule };
128
+ }
129
+
130
+ export interface CommitVerdict {
131
+ /** The bucket the commit lands in. `generated_only` is a diagnostic, not a view bucket. */
132
+ bucket: Category | 'mixed' | 'generated_only';
133
+ categories: Category[];
134
+ has_unclassified: boolean;
135
+ }
136
+
137
+ /** Classify ONE commit from its changed paths. Nothing here reads the subject. */
138
+ export function classifyCommit(paths: readonly string[], tax: Taxonomy): CommitVerdict {
139
+ const kept = paths.filter((p) => !isExcluded(p, tax));
140
+ if (kept.length === 0) {
141
+ return { bucket: 'generated_only', categories: [], has_unclassified: false };
142
+ }
143
+ const seen = new Set<Category>();
144
+ for (const p of kept) seen.add(classifyPath(p, tax));
145
+ const has_unclassified = seen.has('unclassified');
146
+ const classified = [...seen].filter((c) => c !== 'unclassified').sort();
147
+ if (classified.length === 0) return { bucket: 'unclassified', categories: [], has_unclassified: true };
148
+ if (classified.length === 1) return { bucket: classified[0]!, categories: classified, has_unclassified };
149
+ return { bucket: 'mixed', categories: classified, has_unclassified };
150
+ }
151
+
152
+ function git(args: string[], cwd = REPO_ROOT): string {
153
+ const r = spawnSync('git', args, { cwd, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
154
+ if (r.status !== 0) throw new Error(`git ${args.join(' ')} failed: ${(r.stderr || '').trim()}`);
155
+ return r.stdout;
156
+ }
157
+
158
+ export interface Reading {
159
+ schema_version: 1;
160
+ taxonomy_version: string;
161
+ classifier_version: string;
162
+ label: string;
163
+ range: { from: string; to: string; from_sha: string; to_sha: string };
164
+ merge_handling: 'no-merges';
165
+ rename_handling: 'git-default';
166
+ commit_view: Record<string, number>;
167
+ diff_view: Record<string, { added: number; deleted: number }>;
168
+ diagnostics: {
169
+ generated_only_commits: number;
170
+ commits_with_unclassified: number;
171
+ unclassified_paths: string[];
172
+ mixed_combinations: Record<string, number>;
173
+ largest_contributors: Record<string, [string, number][]>;
174
+ };
175
+ response_obligation: { triggered: boolean; rule: string; governance_only: number; consumer_only: number };
176
+ }
177
+
178
+ /** The classifier's own version, bumped independently of the taxonomy's. */
179
+ export const CLASSIFIER_VERSION = '1.0.0';
180
+
181
+ /**
182
+ * The response obligation's trigger, stated once so the gate and the report
183
+ * cannot drift: governance-only commits STRICTLY outnumber consumer-only
184
+ * commits over the span. Strict inequality is the literal reading of the
185
+ * roadmap's own wording and both council seats named it explicitly.
186
+ */
187
+ export const OBLIGATION_RULE = 'governance-only commits > consumer-only commits';
188
+
189
+ export function measureRange(from: string, to: string, tax: Taxonomy, label = '', cwd = REPO_ROOT): Reading {
190
+ const shas = git(['log', '--no-merges', '--format=%H', `${from}..${to}`], cwd)
191
+ .split('\n')
192
+ .map((s) => s.trim())
193
+ .filter(Boolean);
194
+
195
+ const commit_view: Record<string, number> = {
196
+ consumer: 0, governance: 0, maintenance: 0, unclassified: 0, mixed: 0,
197
+ };
198
+ const mixed_combinations: Record<string, number> = {};
199
+ const unclassified_paths = new Set<string>();
200
+ let generated_only_commits = 0;
201
+ let commits_with_unclassified = 0;
202
+
203
+ for (const sha of shas) {
204
+ const paths = git(['show', '--name-only', '--format=', sha], cwd)
205
+ .split('\n').map((s) => s.trim()).filter(Boolean);
206
+ const v = classifyCommit(paths, tax);
207
+ if (v.bucket === 'generated_only') { generated_only_commits += 1; continue; }
208
+ commit_view[v.bucket] = (commit_view[v.bucket] ?? 0) + 1;
209
+ if (v.has_unclassified) commits_with_unclassified += 1;
210
+ if (v.bucket === 'mixed') {
211
+ const key = v.categories.join('+');
212
+ mixed_combinations[key] = (mixed_combinations[key] ?? 0) + 1;
213
+ }
214
+ for (const p of paths) {
215
+ if (!isExcluded(p, tax) && classifyPath(p, tax) === 'unclassified') unclassified_paths.add(p);
216
+ }
217
+ }
218
+
219
+ const diff_view: Record<string, { added: number; deleted: number }> = {
220
+ consumer: { added: 0, deleted: 0 },
221
+ governance: { added: 0, deleted: 0 },
222
+ maintenance: { added: 0, deleted: 0 },
223
+ unclassified: { added: 0, deleted: 0 },
224
+ };
225
+ const touchCount: Record<string, Record<string, number>> = {
226
+ consumer: {}, governance: {}, maintenance: {}, unclassified: {},
227
+ };
228
+
229
+ for (const line of git(['diff', '--numstat', `${from}..${to}`], cwd).split('\n')) {
230
+ const parts = line.split('\t');
231
+ if (parts.length < 3) continue;
232
+ const [addRaw, delRaw, pathRaw] = parts as [string, string, string];
233
+ // `git diff --numstat` renders a rename as `old => new`; take the new path.
234
+ const p = pathRaw.includes('=>') ? pathRaw.replace(/^.*=>\s*/, '').replace(/[{}]/g, '') : pathRaw;
235
+ if (isExcluded(p, tax)) continue;
236
+ const cat = classifyPath(p, tax);
237
+ // A binary file renders as `-`; it contributes a touch, not a line count.
238
+ diff_view[cat]!.added += addRaw === '-' ? 0 : Number(addRaw);
239
+ diff_view[cat]!.deleted += delRaw === '-' ? 0 : Number(delRaw);
240
+ const top = p.split('/').slice(0, 2).join('/');
241
+ touchCount[cat]![top] = (touchCount[cat]![top] ?? 0) + 1;
242
+ }
243
+
244
+ const largest_contributors: Record<string, [string, number][]> = {};
245
+ for (const cat of Object.keys(touchCount)) {
246
+ largest_contributors[cat] = Object.entries(touchCount[cat]!)
247
+ .sort((a, b) => b[1] - a[1])
248
+ .slice(0, 5) as [string, number][];
249
+ }
250
+
251
+ const governance_only = commit_view.governance ?? 0;
252
+ const consumer_only = commit_view.consumer ?? 0;
253
+
254
+ return {
255
+ schema_version: 1,
256
+ taxonomy_version: tax.taxonomy_version,
257
+ classifier_version: CLASSIFIER_VERSION,
258
+ label: label || `${from}..${to}`,
259
+ range: {
260
+ from, to,
261
+ from_sha: git(['rev-parse', from], cwd).trim(),
262
+ to_sha: git(['rev-parse', to], cwd).trim(),
263
+ },
264
+ merge_handling: 'no-merges',
265
+ rename_handling: 'git-default',
266
+ commit_view,
267
+ diff_view,
268
+ diagnostics: {
269
+ generated_only_commits,
270
+ commits_with_unclassified,
271
+ unclassified_paths: [...unclassified_paths].sort().slice(0, 50),
272
+ mixed_combinations,
273
+ largest_contributors,
274
+ },
275
+ response_obligation: {
276
+ triggered: governance_only > consumer_only,
277
+ rule: OBLIGATION_RULE,
278
+ governance_only,
279
+ consumer_only,
280
+ },
281
+ };
282
+ }
283
+
284
+ export function renderReading(r: Reading): string {
285
+ const cv = r.commit_view;
286
+ const lines: string[] = [];
287
+ lines.push(`release-mix · ${r.label} · taxonomy ${r.taxonomy_version} · classifier ${r.classifier_version}`);
288
+ lines.push(` range ${r.range.from_sha.slice(0, 9)}..${r.range.to_sha.slice(0, 9)} (${r.merge_handling})`);
289
+ lines.push(` commit view consumer ${cv.consumer} · governance ${cv.governance} · maintenance ${cv.maintenance} · mixed ${cv.mixed} · unclassified ${cv.unclassified}`);
290
+ for (const cat of ['consumer', 'governance', 'maintenance', 'unclassified'] as const) {
291
+ const d = r.diff_view[cat]!;
292
+ lines.push(` diff view ${cat.padEnd(13)} +${d.added} / -${d.deleted}`);
293
+ }
294
+ lines.push(` diagnostics generated-only commits ${r.diagnostics.generated_only_commits} · commits carrying unclassified paths ${r.diagnostics.commits_with_unclassified}`);
295
+ lines.push(` response owed ${r.response_obligation.triggered ? 'YES' : 'no'} (${r.response_obligation.rule}: ${r.response_obligation.governance_only} vs ${r.response_obligation.consumer_only})`);
296
+ lines.push(' NOTE a level, not a verdict — no threshold is committed to this repository.');
297
+ return lines.join('\n');
298
+ }
299
+
300
+ /**
301
+ * AC-2 — every tracked path resolves to exactly one category, or to a rule that
302
+ * says `unclassified` on purpose. A path that matches NO rule is the failure:
303
+ * it would otherwise be counted as taxonomy uncertainty when it is really a
304
+ * taxonomy hole. Reported grouped by the level at which the taxonomy actually
305
+ * decides, so `src/` shows its subdirectories rather than one aggregate row.
306
+ */
307
+ export function auditTree(tax: Taxonomy, cwd = REPO_ROOT): {
308
+ rows: { unit: string; category: Category; files: number }[];
309
+ unmatched: string[];
310
+ } {
311
+ const byUnit = new Map<string, { category: Category; files: number }>();
312
+ const unmatched: string[] = [];
313
+ for (const f of git(['ls-files'], cwd).split('\n')) {
314
+ const file = f.trim();
315
+ if (!file || isExcluded(file, tax)) continue;
316
+ const d = classifyPathDetailed(file, tax);
317
+ if (!d.matched) {
318
+ unmatched.push(file);
319
+ continue;
320
+ }
321
+ const unit = d.rule.startsWith('governance_contracts:') ? 'docs/contracts/ (exception list)' : d.rule;
322
+ const cur = byUnit.get(unit);
323
+ if (cur) cur.files += 1;
324
+ else byUnit.set(unit, { category: d.category, files: 1 });
325
+ }
326
+ const rows = [...byUnit.entries()]
327
+ .map(([unit, v]) => ({ unit, category: v.category, files: v.files }))
328
+ .sort((a, b) => a.unit.localeCompare(b.unit));
329
+ return { rows, unmatched };
330
+ }
331
+
332
+ function usage(msg: string): never {
333
+ process.stderr.write(`measure_release_mix: ${msg}\n`);
334
+ process.stderr.write('usage: measure_release_mix --from <ref> --to <ref> [--label <tag>] [--json <path>] [--quiet]\n');
335
+ process.stderr.write(' measure_release_mix --audit\n');
336
+ process.exit(2);
337
+ }
338
+
339
+ export function main(argv: readonly string[]): number {
340
+ const args = [...argv];
341
+ const flag = (name: string): string | undefined => {
342
+ const i = args.indexOf(name);
343
+ return i === -1 ? undefined : args[i + 1];
344
+ };
345
+ const tax = loadTaxonomy();
346
+
347
+ if (args.includes('--audit')) {
348
+ const { rows, unmatched } = auditTree(tax);
349
+ for (const r of rows) {
350
+ process.stdout.write(` ${r.category.padEnd(13)} ${String(r.files).padStart(5)} ${r.unit}\n`);
351
+ }
352
+ if (unmatched.length > 0) {
353
+ process.stderr.write(`\nmeasure_release_mix: ${unmatched.length} tracked path(s) match no rule:\n`);
354
+ for (const u of unmatched.slice(0, 20)) process.stderr.write(` ${u}\n`);
355
+ return 1;
356
+ }
357
+ const files = rows.reduce((n, r) => n + r.files, 0);
358
+ process.stdout.write(
359
+ `\nOK — ${files} tracked paths over ${rows.length} classification units, all resolved (taxonomy ${tax.taxonomy_version}).\n`,
360
+ );
361
+ return 0;
362
+ }
363
+
364
+ const from = flag('--from');
365
+ const to = flag('--to');
366
+ if (!from || !to) usage('--from and --to are required');
367
+ const reading = measureRange(from, to, tax, flag('--label') ?? '');
368
+ const jsonPath = flag('--json');
369
+ if (jsonPath) {
370
+ fs.mkdirSync(path.dirname(path.resolve(jsonPath)), { recursive: true });
371
+ fs.writeFileSync(path.resolve(jsonPath), `${JSON.stringify(reading, null, 2)}\n`, 'utf8');
372
+ }
373
+ if (!args.includes('--quiet')) process.stdout.write(`${renderReading(reading)}\n`);
374
+ return 0;
375
+ }
376
+
377
+ if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
378
+ try {
379
+ process.exit(main(process.argv.slice(2)));
380
+ } catch (e) {
381
+ process.stderr.write(`measure_release_mix: ${(e as Error).message}\n`);
382
+ process.exit(2);
383
+ }
384
+ }
@@ -0,0 +1,172 @@
1
+ {
2
+ "taxonomy_version": "1.0.0",
3
+ "updated": "2026-09-04",
4
+ "contract": "docs/decisions/ADR-253-per-pr-user-artifact-gate-declined.md",
5
+ "reader": "src/scripts/measure_release_mix.ts",
6
+ "note": [
7
+ "Path -> category mapping for the release governance-vs-product measurement.",
8
+ "Versioned so a later reading is comparable to an earlier one: a reading records",
9
+ "the taxonomy_version it was computed under, and a bump means prior readings are",
10
+ "recomputed rather than silently compared across two different rulers.",
11
+ "Matching is LONGEST-PREFIX-WINS over the repository-relative path. A path that",
12
+ "matches no rule is reported as `unclassified` -- a first-class state, never a",
13
+ "silent default. `unclassified` alone does not make a commit `mixed`.",
14
+ "The `consumer` label names a SHIPPED SURFACE, not delivered consumer value.",
15
+ "openai/codex-default asked for the label `shipped-product` on exactly that",
16
+ "ground and anthropic/claude-sonnet-4-5 preferred keeping `consumer` for",
17
+ "consistency with the roadmap's own wording; the roadmap's wording is kept and",
18
+ "the objection is recorded here rather than dropped."
19
+ ],
20
+ "categories": {
21
+ "consumer": "Shipped surface an installing consumer receives or invokes: skills, rules, commands, guidelines, packs, the agent-config CLI runtime, and user-facing documentation.",
22
+ "governance": "This repository's own steering layer: roadmaps, evidence ledgers, verdicts, decision records, archive and promotion records, and governance-only contracts.",
23
+ "maintenance": "Repository infrastructure: CI gates, tests, build and release automation, packaging, dependencies, tooling configuration.",
24
+ "unclassified": "No rule matches, or the path is genuinely shared between categories and is left visible rather than assigned."
25
+ },
26
+ "excluded_generated": [
27
+ "dist/",
28
+ ".augment/",
29
+ ".claude/",
30
+ ".cursor/",
31
+ ".clinerules/",
32
+ ".windsurfrules",
33
+ "GEMINI.md",
34
+ ".claude-plugin/",
35
+ ".augment-plugin/",
36
+ "package-lock.json",
37
+ "docs/decisions/INDEX.md",
38
+ "agents/roadmaps/archive/INDEX.md",
39
+ "agents/roadmaps/archive/index.json"
40
+ ],
41
+ "excluded_note": [
42
+ "Generated projections and lockfiles are removed BEFORE a commit is classified,",
43
+ "and are excluded from both the commit view and the diff view. A commit left with",
44
+ "no path after the exclusion is counted in the `generated_only` diagnostic rather",
45
+ "than as maintenance -- otherwise a release's ratio would depend on whether the",
46
+ "regeneration was committed separately from the source edit that caused it."
47
+ ],
48
+ "rules": [
49
+ { "prefix": "src/skills/", "category": "consumer" },
50
+ { "prefix": "src/rules/", "category": "consumer" },
51
+ { "prefix": "src/agent-src/", "category": "consumer" },
52
+ { "prefix": "src/domains/", "category": "consumer" },
53
+ { "prefix": "src/packs/", "category": "consumer" },
54
+ { "prefix": "src/profiles/", "category": "consumer" },
55
+ { "prefix": "src/templates/", "category": "consumer" },
56
+ { "prefix": "src/patterns/", "category": "consumer" },
57
+ { "prefix": "src/subagents/", "category": "consumer" },
58
+ { "prefix": "src/missions/", "category": "consumer" },
59
+ { "prefix": "src/flows/", "category": "consumer" },
60
+ { "prefix": "src/config/", "category": "consumer" },
61
+ { "prefix": "src/cli/", "category": "consumer" },
62
+ { "prefix": "src/install/", "category": "consumer" },
63
+ { "prefix": "src/server/", "category": "consumer" },
64
+ { "prefix": "src/ui/", "category": "consumer" },
65
+ { "prefix": "src/shared/", "category": "unclassified" },
66
+
67
+ { "prefix": "src/scripts/", "category": "maintenance" },
68
+ { "prefix": "src/scripts/ai_council/", "category": "consumer" },
69
+ { "prefix": "src/scripts/ai_team/", "category": "consumer" },
70
+ { "prefix": "src/scripts/ai-video/", "category": "consumer" },
71
+ { "prefix": "src/scripts/ai-image/", "category": "consumer" },
72
+ { "prefix": "src/scripts/media/", "category": "consumer" },
73
+ { "prefix": "src/scripts/hooks/", "category": "consumer" },
74
+ { "prefix": "src/scripts/mcp_server/", "category": "consumer" },
75
+ { "prefix": "src/scripts/skill_tools/", "category": "consumer" },
76
+ { "prefix": "src/scripts/command_suggester/", "category": "consumer" },
77
+ { "prefix": "src/scripts/code_graph/", "category": "consumer" },
78
+ { "prefix": "src/scripts/prediction-pool/", "category": "consumer" },
79
+ { "prefix": "src/scripts/cost/", "category": "consumer" },
80
+
81
+ { "prefix": "agents/", "category": "governance" },
82
+ { "prefix": "internal/", "category": "governance" },
83
+ { "prefix": "provenance/", "category": "governance" },
84
+
85
+ { "prefix": "docs/", "category": "consumer" },
86
+ { "prefix": "docs/decisions/", "category": "governance" },
87
+ { "prefix": "docs/adrs/", "category": "governance" },
88
+ { "prefix": "docs/archive/", "category": "governance" },
89
+ { "prefix": "docs/maintainers/", "category": "governance" },
90
+ { "prefix": "docs/parity/", "category": "governance" },
91
+ { "prefix": "docs/wedge/", "category": "governance" },
92
+ { "prefix": "docs/experiences/", "category": "governance" },
93
+ { "prefix": "docs/case-studies/", "category": "governance" },
94
+ { "prefix": "docs/announcements/", "category": "governance" },
95
+
96
+ { "prefix": "tests/", "category": "maintenance" },
97
+ { "prefix": ".github/", "category": "maintenance" },
98
+ { "prefix": "taskfiles/", "category": "maintenance" },
99
+ { "prefix": "deploy/", "category": "maintenance" },
100
+ { "prefix": "site/", "category": "maintenance" },
101
+ { "prefix": "hooks/", "category": "consumer" },
102
+ { "prefix": "user-types/", "category": "consumer" },
103
+ { "prefix": "tokens/", "category": "consumer" },
104
+ { "prefix": "LICENSES/", "category": "consumer" },
105
+
106
+ { "prefix": "README.md", "category": "consumer" },
107
+ { "prefix": "ONBOARDING.md", "category": "consumer" },
108
+ { "prefix": "MIGRATION.md", "category": "consumer" },
109
+ { "prefix": "BREAKING_CHANGES.md", "category": "consumer" },
110
+ { "prefix": "SECURITY.md", "category": "consumer" },
111
+ { "prefix": "LEGAL_NOTICE.md", "category": "consumer" },
112
+ { "prefix": "CHANGELOG.md", "category": "consumer" },
113
+ { "prefix": "CAPABILITIES.yaml", "category": "consumer" },
114
+ { "prefix": "CREDITS.md", "category": "consumer" },
115
+ { "prefix": "REUSE.toml", "category": "consumer" },
116
+ { "prefix": "LICENSE", "category": "consumer" },
117
+ { "prefix": "NOTICE", "category": "consumer" },
118
+ { "prefix": "llms.txt", "category": "consumer" },
119
+
120
+ { "prefix": "AGENTS.md", "category": "governance" },
121
+ { "prefix": "CLAUDE.md", "category": "governance" },
122
+ { "prefix": "CONTRIBUTING.md", "category": "governance" },
123
+
124
+ { "prefix": "Taskfile.yml", "category": "maintenance" },
125
+ { "prefix": "agent-config", "category": "maintenance" },
126
+ { "prefix": "scripts-run", "category": "maintenance" },
127
+ { "prefix": "setup.sh", "category": "maintenance" },
128
+ { "prefix": "package.json", "category": "maintenance" },
129
+ { "prefix": "eslint.config.js", "category": "maintenance" },
130
+ { "prefix": "tsconfig", "category": "maintenance" },
131
+ { "prefix": "vite.config.ts", "category": "maintenance" },
132
+ { "prefix": "vitest.config.ts", "category": "maintenance" },
133
+ { "prefix": "playwright.config.ts", "category": "maintenance" },
134
+ { "prefix": "glama.json", "category": "maintenance" },
135
+ { "prefix": "mcp.json", "category": "maintenance" },
136
+ { "prefix": "allowlist_backup.json", "category": "maintenance" },
137
+ { "prefix": ".augmentignore", "category": "maintenance" },
138
+ { "prefix": ".dockerignore", "category": "maintenance" },
139
+ { "prefix": ".gitattributes", "category": "maintenance" },
140
+ { "prefix": ".gitignore", "category": "maintenance" },
141
+ { "prefix": ".mailmap", "category": "maintenance" },
142
+ { "prefix": ".npmignore", "category": "maintenance" },
143
+ { "prefix": ".path-leak-allow", "category": "maintenance" },
144
+ { "prefix": ".secret-allow", "category": "maintenance" }
145
+ ],
146
+ "governance_contracts": [
147
+ "docs/contracts/adr-layout.md",
148
+ "docs/contracts/adr-architectural-consensus-mechanism.md",
149
+ "docs/contracts/ai-council-config.md",
150
+ "docs/contracts/adversarial-review-protocol.md",
151
+ "docs/contracts/branch-protection-policy.md",
152
+ "docs/contracts/ci-green-floor.md",
153
+ "docs/contracts/ci-cost-budget.md",
154
+ "docs/contracts/release-pr-gating.md",
155
+ "docs/contracts/release-sizing.md",
156
+ "docs/contracts/release-trunk-sync.md",
157
+ "docs/contracts/CHANGELOG-conventions.md",
158
+ "docs/contracts/evidence-artifact-types.md",
159
+ "docs/contracts/kernel-membership.md",
160
+ "docs/contracts/package-self-orientation.md"
161
+ ],
162
+ "governance_contracts_note": [
163
+ "`docs/contracts/` defaults to `consumer` because most contracts on that page",
164
+ "describe behavior the package promises an installing consumer -- classifying the",
165
+ "whole directory as governance would systematically undercount shipped work.",
166
+ "The files listed above are the exceptions: contracts that govern this",
167
+ "repository's own process (decision recording, council, release cutting, CI",
168
+ "floors, evidence discipline) rather than promised behavior. They resolve to",
169
+ "`governance`. A contract absent from this list and absent from the tree is a",
170
+ "stale entry and is reported by `--audit`."
171
+ ]
172
+ }