@event4u/agent-config 8.10.0 → 8.12.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 (226) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/AGENTS.md +1 -0
  3. package/CHANGELOG.md +67 -149
  4. package/README.md +6 -6
  5. package/dist/agent-src/commands/challenge-me/vision.md +121 -32
  6. package/dist/agent-src/commands/challenge-me/with-docs.md +21 -8
  7. package/dist/agent-src/commands/commit/in-chunks.md +1 -1
  8. package/dist/agent-src/commands/commit.md +2 -1
  9. package/dist/agent-src/commands/cost/report.md +2 -1
  10. package/dist/agent-src/commands/feature/dev.md +4 -4
  11. package/dist/agent-src/commands/humanize.md +101 -0
  12. package/dist/agent-src/commands/jira-ticket.md +2 -2
  13. package/dist/agent-src/commands/memory/load.md +15 -0
  14. package/dist/agent-src/commands/optimize/project.md +12 -0
  15. package/dist/agent-src/commands/roadmap/create.md +12 -11
  16. package/dist/agent-src/commands/tests/create.md +1 -1
  17. package/dist/agent-src/commands/worktree/cleanup.md +22 -17
  18. package/dist/agent-src/commands/worktree/create.md +3 -1
  19. package/dist/agent-src/commands/worktree/status.md +12 -0
  20. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +66 -0
  21. package/dist/agent-src/contexts/execution/orchestration-benchmark-gate.md +1 -0
  22. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +47 -3
  23. package/dist/agent-src/contexts/execution/roadmap-ci-steps-mechanics.md +117 -0
  24. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +111 -85
  25. package/dist/agent-src/contexts/execution/roadmap-writing-source-derived.md +65 -0
  26. package/dist/agent-src/contexts/execution/subagent-modes-detail.md +84 -0
  27. package/dist/agent-src/rules/architecture.md +6 -51
  28. package/dist/agent-src/rules/autonomous-execution.md +7 -37
  29. package/dist/agent-src/rules/broken-access-control.md +7 -45
  30. package/dist/agent-src/rules/code-comment-discipline.md +64 -0
  31. package/dist/agent-src/rules/context-hygiene.md +14 -0
  32. package/dist/agent-src/rules/decision-revisit-gate.md +9 -77
  33. package/dist/agent-src/rules/delegation-policy.md +10 -8
  34. package/dist/agent-src/rules/design-fidelity.md +4 -0
  35. package/dist/agent-src/rules/git-history-discipline.md +4 -79
  36. package/dist/agent-src/rules/improve-before-implement.md +7 -66
  37. package/dist/agent-src/rules/legal-safety-floor.md +11 -55
  38. package/dist/agent-src/rules/no-roadmap-references.md +13 -77
  39. package/dist/agent-src/rules/persona-governance.md +5 -47
  40. package/dist/agent-src/rules/provider-lifecycle-discipline.md +6 -32
  41. package/dist/agent-src/rules/roadmap-ci-steps-policy.md +21 -104
  42. package/dist/agent-src/rules/roadmap-progress-sync.md +5 -56
  43. package/dist/agent-src/rules/think-before-action.md +10 -0
  44. package/dist/agent-src/rules/untrusted-input-defense.md +3 -75
  45. package/dist/agent-src/rules/verify-before-complete.md +12 -0
  46. package/dist/agent-src/skills/agent-docs-writing/SKILL.md +96 -12
  47. package/dist/agent-src/skills/ai-council/SKILL.md +19 -9
  48. package/dist/agent-src/skills/api-design/SKILL.md +7 -5
  49. package/dist/agent-src/skills/api-testing/SKILL.md +1 -1
  50. package/dist/agent-src/skills/authz-review/SKILL.md +59 -2
  51. package/dist/agent-src/skills/blast-radius-analyzer/SKILL.md +1 -1
  52. package/dist/agent-src/skills/bug-analyzer/SKILL.md +19 -16
  53. package/dist/agent-src/skills/code-refactoring/SKILL.md +9 -9
  54. package/dist/agent-src/skills/content-funnel-design/SKILL.md +2 -0
  55. package/dist/agent-src/skills/data-flow-mapper/SKILL.md +1 -1
  56. package/dist/agent-src/skills/decision-review/SKILL.md +86 -0
  57. package/dist/agent-src/skills/defense-in-depth/SKILL.md +2 -2
  58. package/dist/agent-src/skills/dependency-upgrade/SKILL.md +7 -7
  59. package/dist/agent-src/skills/design-intelligence/SKILL.md +8 -8
  60. package/dist/agent-src/skills/design-review/SKILL.md +23 -2
  61. package/dist/agent-src/skills/doc-coauthoring/SKILL.md +2 -0
  62. package/dist/agent-src/skills/editorial-calendar/SKILL.md +2 -0
  63. package/dist/agent-src/skills/fe-design/SKILL.md +7 -9
  64. package/dist/agent-src/skills/git-workflow/SKILL.md +86 -0
  65. package/dist/agent-src/skills/humanizer/SKILL.md +147 -0
  66. package/dist/agent-src/skills/humanizer/data/patterns.md +190 -0
  67. package/dist/agent-src/skills/humanizer/evals/triggers.json +17 -0
  68. package/dist/agent-src/skills/laravel/SKILL.md +14 -1
  69. package/dist/agent-src/skills/launch-readiness/SKILL.md +20 -19
  70. package/dist/agent-src/skills/legal-practice-profile/SKILL.md +126 -0
  71. package/dist/agent-src/skills/logging-monitoring/SKILL.md +1 -1
  72. package/dist/agent-src/skills/mcp/SKILL.md +1 -1
  73. package/dist/agent-src/skills/memory-consolidation/SKILL.md +14 -1
  74. package/dist/agent-src/skills/migration-architect/SKILL.md +2 -1
  75. package/dist/agent-src/skills/module-detect-on-the-fly/SKILL.md +58 -1
  76. package/dist/agent-src/skills/performance/SKILL.md +1 -1
  77. package/dist/agent-src/skills/php-debugging/SKILL.md +17 -0
  78. package/dist/agent-src/skills/readme-writing/SKILL.md +2 -0
  79. package/dist/agent-src/skills/readme-writing-package/SKILL.md +2 -0
  80. package/dist/agent-src/skills/release-comms/SKILL.md +2 -0
  81. package/dist/agent-src/skills/roadmap-writing/SKILL.md +22 -51
  82. package/dist/agent-src/skills/security-audit/SKILL.md +6 -9
  83. package/dist/agent-src/skills/sentry-integration/SKILL.md +2 -2
  84. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +8 -0
  85. package/dist/agent-src/skills/source-discovery/SKILL.md +26 -24
  86. package/dist/agent-src/skills/subagent-orchestration/SKILL.md +55 -46
  87. package/dist/agent-src/skills/subagent-orchestration/evals/evals.json +42 -0
  88. package/dist/agent-src/skills/systematic-debugging/SKILL.md +10 -0
  89. package/dist/agent-src/skills/using-git-worktrees/SKILL.md +10 -0
  90. package/dist/agent-src/skills/worktree-lifecycle/SKILL.md +27 -20
  91. package/dist/agent-src/skills/worktree-lifecycle/evals/evals.json +38 -0
  92. package/dist/agent-src/templates/agent-settings.md +3 -2
  93. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +15 -1
  94. package/dist/agent-src/templates/contexts/knowledge-card.md +12 -0
  95. package/dist/agent-src/templates/roadmaps.md +14 -0
  96. package/dist/cli/commands/recordTriggerEval.js +4 -14
  97. package/dist/cli/commands/recordTriggerEval.js.map +1 -1
  98. package/dist/discovery/deprecation-report.md +1 -1
  99. package/dist/discovery/discovery-manifest.json +157 -88
  100. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  101. package/dist/discovery/discovery-manifest.summary.md +6 -6
  102. package/dist/discovery/orphan-report.md +1 -1
  103. package/dist/discovery/packs.json +13 -10
  104. package/dist/discovery/trust-report.md +3 -3
  105. package/dist/discovery/workspaces.json +14 -9
  106. package/dist/install/install.mjs +12 -7
  107. package/dist/mcp/registry-manifest.json +4 -4
  108. package/dist/router.json +1 -1
  109. package/dist/scripts/_lib/claude_settings_hooks.js +8 -0
  110. package/dist/scripts/_lib/claude_settings_hooks.js.map +1 -1
  111. package/dist/scripts/_lib/trigger_eval_floors.js +24 -0
  112. package/dist/scripts/_lib/trigger_eval_floors.js.map +1 -0
  113. package/dist/scripts/condense.js +12 -2
  114. package/dist/scripts/condense.js.map +1 -1
  115. package/dist/server/schemas/settings.js +5 -2
  116. package/dist/server/schemas/settings.js.map +1 -1
  117. package/docs/CLAIMS.md +54 -5
  118. package/docs/architecture.md +4 -4
  119. package/docs/archive/CHANGELOG-pre-8.12.0.md +304 -0
  120. package/docs/benchmark.md +90 -0
  121. package/docs/catalog.md +8 -5
  122. package/docs/command-flows.md +2 -1
  123. package/docs/contracts/ai-council-config.md +97 -0
  124. package/docs/contracts/command-clusters.md +1 -0
  125. package/docs/contracts/evidence-based-pruning.md +1 -0
  126. package/docs/contracts/hook-architecture-v1.md +25 -0
  127. package/docs/contracts/kernel-membership.md +35 -0
  128. package/docs/contracts/measurement-baseline.md +1 -0
  129. package/docs/contracts/persona-schema.md +49 -0
  130. package/docs/contracts/provider-lifecycle.md +29 -4
  131. package/docs/contracts/release-sizing.md +22 -0
  132. package/docs/contracts/write-engine.md +38 -1
  133. package/docs/customization.md +12 -0
  134. package/docs/decisions/ADR-002-kernel-bucket-overrides.md +11 -0
  135. package/docs/decisions/ADR-003-flat-cluster-subs-and-colon-syntax.md +11 -0
  136. package/docs/decisions/ADR-006-skill-tools-python-pilot.md +1 -1
  137. package/docs/decisions/ADR-028-root-layout.md +1 -1
  138. package/docs/decisions/ADR-038-canonical-settings-path.md +10 -0
  139. package/docs/decisions/ADR-092-defer-command-tier-alias-removal.md +29 -5
  140. package/docs/decisions/ADR-093-ai-council-config-user-global.md +1 -1
  141. package/docs/decisions/ADR-100-global-knowledge-card-sharing.md +7 -0
  142. package/docs/decisions/ADR-103-global-knowledge-default-off-until-measured.md +2 -2
  143. package/docs/decisions/ADR-105-automatic-subagent-orchestration.md +1 -1
  144. package/docs/decisions/ADR-114-commit-policy-iron-law-override.md +11 -0
  145. package/docs/decisions/ADR-116-memory-tripwire-activation-path.md +11 -1
  146. package/docs/decisions/ADR-117-subagents-auto-default-on.md +2 -1
  147. package/docs/decisions/ADR-118-loop-engineering-boundaries.md +162 -0
  148. package/docs/decisions/ADR-119-global-knowledge-default-on.md +104 -0
  149. package/docs/decisions/ADR-120-council-chairman-mode.md +70 -0
  150. package/docs/decisions/ADR-121-knowledge-sensitivity-classes.md +90 -0
  151. package/docs/decisions/ADR-200-python-to-typescript-migration.md +11 -1
  152. package/docs/decisions/ADR-rule-kernel-and-router.md +11 -0
  153. package/docs/decisions/INDEX.md +13 -9
  154. package/docs/design/council-vs-solo-baseline.md +92 -0
  155. package/docs/design/cross-model-parity-eval.md +82 -0
  156. package/docs/design/length-neutral-judge-rerun.md +92 -0
  157. package/docs/design/utilization-window-criteria.md +51 -0
  158. package/docs/featured-skills.md +2 -2
  159. package/docs/getting-started-by-role.md +1 -1
  160. package/docs/getting-started.md +2 -2
  161. package/docs/governance-advantage.md +1 -1
  162. package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +68 -0
  163. package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +80 -5
  164. package/docs/guidelines/agent-infra/rule-body-migration-inventory.md +241 -0
  165. package/docs/guidelines/agent-infra/untrusted-input-spotlighting.md +80 -3
  166. package/docs/guidelines/code-clarity.md +191 -1
  167. package/docs/guidelines/design-antipatterns.md +20 -0
  168. package/docs/guidelines/php/php-coding-patterns.md +12 -2
  169. package/docs/maintainers/branch-protection.md +87 -0
  170. package/docs/maintainers/system-map.md +85 -0
  171. package/docs/proof.md +32 -15
  172. package/docs/wedge/production-validator/production-validator.md +27 -0
  173. package/package.json +1 -1
  174. package/src/config/agent-settings.template.yml +38 -9
  175. package/src/scripts/_cli/cmd_doctor.ts +129 -1
  176. package/src/scripts/_lib/auto_dispatch.corpus.test.ts +87 -0
  177. package/src/scripts/_lib/claude_settings_hooks.ts +8 -0
  178. package/src/scripts/_lib/knowledge_global.ts +50 -2
  179. package/src/scripts/_lib/knowledge_global_promote.ts +150 -0
  180. package/src/scripts/_lib/knowledge_global_redaction.ts +42 -4
  181. package/src/scripts/_lib/lexical_index.ts +15 -2
  182. package/src/scripts/_lib/orchestration_record.ts +14 -0
  183. package/src/scripts/_lib/trigger_eval_floors.ts +31 -0
  184. package/src/scripts/ai_council/chairman.ts +90 -0
  185. package/src/scripts/ai_council/clients.ts +18 -3
  186. package/src/scripts/ai_council/config.ts +157 -0
  187. package/src/scripts/ai_council/debate_gates.ts +89 -0
  188. package/src/scripts/ai_council/orchestrator.ts +252 -2
  189. package/src/scripts/ai_council/prompts.ts +121 -11
  190. package/src/scripts/ai_council/replay.ts +17 -0
  191. package/src/scripts/ai_council/stance_tally.ts +184 -0
  192. package/src/scripts/ai_tells_rules.ts +433 -0
  193. package/src/scripts/bench_humanizer_eval.ts +240 -0
  194. package/src/scripts/bench_persona_placebo.ts +308 -0
  195. package/src/scripts/bench_quality_rerun.ts +357 -0
  196. package/src/scripts/build_discovery_manifest.ts +6 -0
  197. package/src/scripts/build_proof.ts +30 -9
  198. package/src/scripts/check_artefact_count_messaging.ts +56 -0
  199. package/src/scripts/check_claims.ts +40 -0
  200. package/src/scripts/check_knowledge_cards.ts +33 -4
  201. package/src/scripts/check_portability.ts +1 -0
  202. package/src/scripts/check_rule_invariants.ts +10 -3
  203. package/src/scripts/complexity_report.ts +776 -0
  204. package/src/scripts/condense.ts +16 -2
  205. package/src/scripts/consumer_matrix.ts +292 -14
  206. package/src/scripts/council_cli.ts +203 -1
  207. package/src/scripts/detect_ai_tells.ts +312 -0
  208. package/src/scripts/explain_run.ts +593 -0
  209. package/src/scripts/install.ts +12 -0
  210. package/src/scripts/knowledge_global_cli.ts +235 -46
  211. package/src/scripts/lint_framework_leakage.ts +88 -7
  212. package/src/scripts/lint_framework_leakage_allowlist.json +59 -572
  213. package/src/scripts/lint_knowledge_scale.ts +30 -0
  214. package/src/scripts/lint_originality_shingles.ts +210 -0
  215. package/src/scripts/orchestration_record.ts +1 -0
  216. package/src/scripts/rule_backlinks.ts +0 -0
  217. package/src/scripts/schemas/agent-settings.schema.json +11 -0
  218. package/src/scripts/self_review_gate.ts +41 -15
  219. package/src/scripts/skill_eval_coverage.ts +71 -3
  220. package/src/scripts/skill_linter.ts +1 -0
  221. package/src/scripts/trigger_eval_rotation.ts +344 -0
  222. package/src/scripts/update_counts.ts +5 -17
  223. package/src/scripts/worktree_cleanup_check.ts +263 -0
  224. package/src/server/schemas/settings.ts +8 -3
  225. package/src/scripts/analysis_freshness.ts +0 -362
  226. package/src/scripts/export_replay_corpus.ts +0 -208
@@ -166,6 +166,36 @@ export function runChecks(root: string): Warning[] {
166
166
  });
167
167
  }
168
168
 
169
+ // 6. Contested cards — surface knowledge cards flagged `contested: true`
170
+ // (set when `check_memory_contradiction.ts` fires) so a weak / disputed
171
+ // claim stays visibly weak across sessions instead of silently hardening.
172
+ const cardsDir = path.join(root, 'agents', 'knowledge');
173
+ const contested = listFiles(cardsDir, ['.md'])
174
+ .filter((f) => {
175
+ const base = path.basename(f).toLowerCase();
176
+ return base !== 'readme.md' && base !== 'index.md';
177
+ })
178
+ .filter((f) => {
179
+ let text: string;
180
+ try {
181
+ text = fs.readFileSync(f, 'utf-8');
182
+ } catch {
183
+ return false;
184
+ }
185
+ // frontmatter-scoped: `contested: true` on its own line near the top.
186
+ return /^\s*contested:\s*true\s*$/m.test(text.slice(0, 2000));
187
+ })
188
+ .map((f) => path.basename(f));
189
+ if (contested.length > 0) {
190
+ warnings.push({
191
+ rule: 'contested-cards',
192
+ metric: `${contested.length}/0`,
193
+ message:
194
+ `${contested.length} knowledge card(s) carry \`contested: true\`: ${contested.join(', ')}. ` +
195
+ 'Resolution path: reconcile each against its `contradictions: [id]` list (human-judged) — a contested claim must not be cited as settled until resolved.',
196
+ });
197
+ }
198
+
169
199
  // 5. Hot-context budget.
170
200
  const hotContext = path.join(root, 'agents', 'runtime', 'state', 'hot-context.md');
171
201
  let hotText = '';
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * lint_originality_shingles — mechanical re-skin detector for the artifact
4
+ * corpus (road-to-opt-subagent-harvest P1.3).
5
+ *
6
+ * Compares every pair of skills / personas / subagents by entity-neutralized
7
+ * 8-word shingle overlap. Two artifacts that differ only in the named
8
+ * framework, product, or persona ("re-skins") share almost all shingles once
9
+ * entities are masked — exactly the near-duplicate class a human review
10
+ * misses at 200+ files. Slots beside `persona-governance` / `skill-quality`.
11
+ *
12
+ * Sibling of `lint_skill_originality` (ADR-096), NOT a replacement: that
13
+ * gate runs bag-of-words Jaccard over SKILLS with pack-domain severity;
14
+ * this one masks entities and compares 8-word PHRASES across skills +
15
+ * personas + subagents — the re-skin that only swaps "Laravel" for
16
+ * "Django" scores LOW on plain token-Jaccard (different entity tokens)
17
+ * and HIGH here. Run both.
18
+ *
19
+ * Modes:
20
+ * default report-only — prints the top overlapping pairs,
21
+ * always exits 0 (first-run calibration mode).
22
+ * --threshold <pct> exits 1 when any pair's overlap (Jaccard, %) is
23
+ * >= pct and the pair is not allowlisted below.
24
+ * --top <n> rows to print (default 15).
25
+ * --quiet suppress the report (threshold mode still fails).
26
+ *
27
+ * Known-legitimate pairs (shared scaffolding, sibling carve-outs) are
28
+ * allowlisted inline with reasons — same >20-entries policy as every other
29
+ * allowlist in this repo.
30
+ */
31
+ import * as fs from 'node:fs';
32
+ import * as path from 'node:path';
33
+ import { fileURLToPath, pathToFileURL } from 'node:url';
34
+
35
+ const _HERE = path.dirname(fileURLToPath(import.meta.url));
36
+ const REPO = path.resolve(_HERE, '..', '..');
37
+
38
+ const SHINGLE_LEN = 8;
39
+
40
+ /** Entity mask — named stacks/products/personas collapse to one token so a
41
+ * re-skin that only swaps the entity still matches. Lowercase input. */
42
+ const ENTITY_RE = new RegExp(
43
+ '\\b(laravel|symfony|django|rails|react|vue|angular|nextjs|next\\.js|nuxt|' +
44
+ 'nestjs|express|fastify|flask|fastapi|spring|eloquent|doctrine|prisma|' +
45
+ 'pest|phpunit|jest|vitest|pytest|phpstan|rector|ecs|prettier|eslint|' +
46
+ 'mypy|tsc|php|python|typescript|javascript|node|go|rust|ruby|java|' +
47
+ 'tailwind|blade|livewire|flux|jira|sentry|grafana|terraform|terragrunt|' +
48
+ 'traefik|docker|aws|stripe|openai|anthropic|claude|gemini|gpt)\\b',
49
+ 'g',
50
+ );
51
+
52
+ interface Doc {
53
+ id: string;
54
+ shingles: Set<string>;
55
+ }
56
+
57
+ interface AllowPair {
58
+ a: string;
59
+ b: string;
60
+ reason: string;
61
+ }
62
+
63
+ /** Sibling artifacts that legitimately share scaffolding. Keep < 20. */
64
+ const ALLOWLIST: AllowPair[] = [
65
+ // Populated from calibration runs; empty on first ship — the default
66
+ // mode is report-only, so no entry is needed until a threshold is armed.
67
+ ];
68
+
69
+ function _normalize(body: string): string[] {
70
+ // strip fenced code, frontmatter, markdown syntax; mask entities.
71
+ let text = body.replace(/^---\n[\s\S]*?\n---\n/, '');
72
+ text = text.replace(/```[\s\S]*?```/g, ' ');
73
+ text = text.toLowerCase();
74
+ text = text.replace(ENTITY_RE, '§entity§');
75
+ text = text.replace(/[^a-z0-9§]+/g, ' ');
76
+ return text.split(' ').filter((w) => w.length > 0);
77
+ }
78
+
79
+ function _shingles(words: string[]): Set<string> {
80
+ const out = new Set<string>();
81
+ for (let i = 0; i + SHINGLE_LEN <= words.length; i++) {
82
+ out.add(words.slice(i, i + SHINGLE_LEN).join(' '));
83
+ }
84
+ return out;
85
+ }
86
+
87
+ function _collect(): Doc[] {
88
+ const docs: Doc[] = [];
89
+ const push = (id: string, file: string): void => {
90
+ let body: string;
91
+ try {
92
+ body = fs.readFileSync(file, 'utf-8');
93
+ } catch {
94
+ return;
95
+ }
96
+ const sh = _shingles(_normalize(body));
97
+ if (sh.size >= 20) {
98
+ docs.push({ id, shingles: sh });
99
+ }
100
+ };
101
+ const skillsDir = path.join(REPO, 'src', 'skills');
102
+ for (const d of fs.readdirSync(skillsDir, { withFileTypes: true })) {
103
+ if (d.isDirectory()) {
104
+ push(`skill:${d.name}`, path.join(skillsDir, d.name, 'SKILL.md'));
105
+ }
106
+ }
107
+ const personasDir = path.join(REPO, 'src', 'personas');
108
+ if (fs.existsSync(personasDir)) {
109
+ const walk = (dir: string, prefix: string): void => {
110
+ for (const d of fs.readdirSync(dir, { withFileTypes: true })) {
111
+ const full = path.join(dir, d.name);
112
+ if (d.isDirectory() && !d.name.startsWith('_')) {
113
+ walk(full, prefix);
114
+ } else if (d.isFile() && d.name.endsWith('.md') && d.name !== 'README.md') {
115
+ push(`persona:${path.basename(d.name, '.md')}`, full);
116
+ }
117
+ }
118
+ };
119
+ walk(personasDir, 'persona');
120
+ }
121
+ const subagentsDir = path.join(REPO, 'src', 'subagents');
122
+ if (fs.existsSync(subagentsDir)) {
123
+ for (const f of fs.readdirSync(subagentsDir)) {
124
+ if (f.endsWith('.md') && !f.startsWith('_')) {
125
+ push(`subagent:${path.basename(f, '.md')}`, path.join(subagentsDir, f));
126
+ }
127
+ }
128
+ }
129
+ return docs;
130
+ }
131
+
132
+ function _jaccardPct(a: Set<string>, b: Set<string>): number {
133
+ const [small, large] = a.size <= b.size ? [a, b] : [b, a];
134
+ let inter = 0;
135
+ for (const s of small) {
136
+ if (large.has(s)) inter++;
137
+ }
138
+ const union = a.size + b.size - inter;
139
+ return union === 0 ? 0 : (100 * inter) / union;
140
+ }
141
+
142
+ function _allowlisted(a: string, b: string): boolean {
143
+ return ALLOWLIST.some(
144
+ (p) => (p.a === a && p.b === b) || (p.a === b && p.b === a),
145
+ );
146
+ }
147
+
148
+ export function main(argv: string[] = process.argv.slice(2)): number {
149
+ let threshold: number | null = null;
150
+ let top = 15;
151
+ let quiet = false;
152
+ for (let i = 0; i < argv.length; i++) {
153
+ const arg = argv[i] as string;
154
+ if (arg === '--threshold') threshold = Number(argv[++i]);
155
+ else if (arg === '--top') top = Number(argv[++i]);
156
+ else if (arg === '--quiet') quiet = true;
157
+ }
158
+
159
+ const docs = _collect();
160
+ const pairs: Array<{ a: string; b: string; pct: number }> = [];
161
+ for (let i = 0; i < docs.length; i++) {
162
+ for (let j = i + 1; j < docs.length; j++) {
163
+ const pct = _jaccardPct((docs[i] as Doc).shingles, (docs[j] as Doc).shingles);
164
+ if (pct > 0.5) {
165
+ pairs.push({ a: (docs[i] as Doc).id, b: (docs[j] as Doc).id, pct });
166
+ }
167
+ }
168
+ }
169
+ pairs.sort((x, y) => y.pct - x.pct);
170
+
171
+ if (!quiet) {
172
+ process.stdout.write(
173
+ `originality-shingles: ${docs.length} docs · ${SHINGLE_LEN}-word entity-masked shingles\n`,
174
+ );
175
+ for (const p of pairs.slice(0, top)) {
176
+ process.stdout.write(` ${p.pct.toFixed(1).padStart(5)}% ${p.a} ↔ ${p.b}\n`);
177
+ }
178
+ if (pairs.length === 0) {
179
+ process.stdout.write(' no pair above 0.5% overlap\n');
180
+ }
181
+ }
182
+
183
+ if (threshold !== null && Number.isFinite(threshold)) {
184
+ const offenders = pairs.filter((p) => p.pct >= (threshold as number) && !_allowlisted(p.a, p.b));
185
+ if (offenders.length > 0) {
186
+ process.stderr.write(
187
+ `❌ originality-shingles: ${offenders.length} pair(s) at or above ${threshold}% overlap:\n`,
188
+ );
189
+ for (const p of offenders.slice(0, top)) {
190
+ process.stderr.write(` ${p.pct.toFixed(1)}% ${p.a} ↔ ${p.b}\n`);
191
+ }
192
+ return 1;
193
+ }
194
+ }
195
+ return 0;
196
+ }
197
+
198
+ function _isCliEntry(): boolean {
199
+ const a = process.argv[1];
200
+ if (!a) return false;
201
+ if (pathToFileURL(path.resolve(a)).href === import.meta.url) return true;
202
+ try {
203
+ return fs.realpathSync(a) === fs.realpathSync(fileURLToPath(import.meta.url));
204
+ } catch {
205
+ return false;
206
+ }
207
+ }
208
+ if (_isCliEntry()) {
209
+ process.exit(main());
210
+ }
@@ -112,6 +112,7 @@ export function main(argv: string[] = process.argv.slice(2)): number {
112
112
  tier_chosen: str(flags, 'tier-chosen') as TierChosen | undefined,
113
113
  tier_source: str(flags, 'tier-source') as TierSource | undefined,
114
114
  task_class: str(flags, 'task-class'),
115
+ dispatch_mode: str(flags, 'dispatch-mode') as never,
115
116
  task_size_estimate: int(flags, 'task-size-estimate'),
116
117
  wall_clock_ms: int(flags, 'wall-clock-ms'),
117
118
  dispatch_tokens: int(flags, 'dispatch-tokens'),
Binary file
@@ -84,6 +84,17 @@
84
84
  }
85
85
  }
86
86
  },
87
+ "code_style": {
88
+ "type": "object",
89
+ "additionalProperties": true,
90
+ "properties": {
91
+ "docblocks": {
92
+ "type": "string",
93
+ "enum": ["minimal", "full"],
94
+ "description": "Consumed by the code-comment-discipline rule. minimal (default) = no signature-mirroring docblocks; docblocks only for machine-relevant precision (generics, array shapes) or genuine why-context. full = exported public library surface may carry one-line summary docblocks; the redundancy ban still holds."
95
+ }
96
+ }
97
+ },
87
98
  "tokens": {
88
99
  "type": "object",
89
100
  "additionalProperties": true,
@@ -21,7 +21,7 @@
21
21
  * the `check_quality_regression.gateVerdict` pattern.
22
22
  */
23
23
  import { spawnSync } from 'node:child_process';
24
- import { existsSync, readFileSync } from 'node:fs';
24
+ import { appendFileSync, existsSync, readFileSync } from 'node:fs';
25
25
  import path from 'node:path';
26
26
  import { fileURLToPath } from 'node:url';
27
27
 
@@ -244,27 +244,53 @@ export function main(argv: string[]): 0 | 2 {
244
244
 
245
245
  const key = resolveKey();
246
246
  if (!key) {
247
+ // Explicit NEUTRAL state — never a bare green that reads as "reviewed".
247
248
  process.stdout.write(
248
- '::notice::self-review-gate skipped — set the ANTHROPIC_API_KEY repo secret to enable the live dogfooded review.\n',
249
+ '::warning::self-review-gate NEUTRAL — no ANTHROPIC_API_KEY configured, NOTHING was reviewed. ' +
250
+ 'Set the repo secret to enable the live dogfooded review.\n',
249
251
  );
252
+ const summary = process.env.GITHUB_STEP_SUMMARY;
253
+ if (summary) {
254
+ appendFileSync(
255
+ summary,
256
+ '### Self-review gate: NEUTRAL\n\nNo `ANTHROPIC_API_KEY` secret — nothing was reviewed. This is not a pass.\n',
257
+ );
258
+ }
250
259
  return 0;
251
260
  }
252
261
 
253
- const client = new AnthropicClient({ api_key: key });
254
- const resp = client.ask(buildSystemPrompt(), diffText(baseRef, plan.files), 4096);
255
- if (resp.error) {
256
- process.stdout.write(`::warning::self-review-gate — model call failed: ${resp.error}\n`);
257
- return 0; // advisory: a transport failure never blocks the merge
258
- }
259
- const findings = parseFindings(resp.text);
260
- const body = renderReview(findings, enforce);
261
- postReview(body);
262
+ // Belt-and-suspenders: the WHOLE live path is wrapped so that ANYTHING going
263
+ // wrong — no API credit / balance (HTTP 402), rate-limit (429), network error,
264
+ // a client-ctor throw, a parse failure — resolves to a NEUTRAL exit 0, never a
265
+ // red CI. A missing key or no-credit must never become a merge blocker; the
266
+ // gate is advisory by design. (Only an --enforce run with real blocking
267
+ // findings returns 2 — an error is not a finding.)
268
+ try {
269
+ const client = new AnthropicClient({ api_key: key });
270
+ const resp = client.ask(buildSystemPrompt(), diffText(baseRef, plan.files), 4096);
271
+ if (resp.error) {
272
+ process.stdout.write(
273
+ `::warning::self-review-gate NEUTRAL — model call did not complete (${resp.error}); ` +
274
+ 'nothing was reviewed, not a blocker.\n',
275
+ );
276
+ return 0; // no credit / transport / API error → never blocks the merge
277
+ }
278
+ const findings = parseFindings(resp.text);
279
+ const body = renderReview(findings, enforce);
280
+ postReview(body);
262
281
 
263
- const code = gateVerdict(findings, { enforce });
264
- if (code === 2) {
265
- process.stdout.write('::error::self-review-gate — merge-blocking findings under enforce mode.\n');
282
+ const code = gateVerdict(findings, { enforce });
283
+ if (code === 2) {
284
+ process.stdout.write('::error::self-review-gate — merge-blocking findings under enforce mode.\n');
285
+ }
286
+ return code;
287
+ } catch (e) {
288
+ process.stdout.write(
289
+ `::warning::self-review-gate NEUTRAL — unexpected error (${e instanceof Error ? e.message : String(e)}); ` +
290
+ 'nothing was reviewed, not a blocker.\n',
291
+ );
292
+ return 0;
266
293
  }
267
- return code;
268
294
  }
269
295
 
270
296
  if (existsSync(process.argv[1] ?? '') && import.meta.url === `file://${process.argv[1]}`) {
@@ -26,6 +26,12 @@
26
26
  * drops below the pinned floor (internal/evals/coverage-floor.json).
27
27
  * A missing floor file is treated as an all-zero floor
28
28
  * (bootstrap-inert: never fails before the first --write-floor).
29
+ * ALSO enforces the tier floor (road-to-fable-feedback-5
30
+ * Phase 4a, council 2026-07-10): every `priority`-tier skill
31
+ * (rich + default-surface + router) MUST carry a behavioural
32
+ * evals.json, unless exempted WITH a reason in
33
+ * internal/evals/tier-floor-exemptions.json. No silent
34
+ * exemptions — the bright line replaces weighted scoring.
29
35
  * --write-floor pin the current coverage as the new floor. Maintainer action
30
36
  * after authoring evals — the ratchet only ever rises.
31
37
  *
@@ -47,6 +53,7 @@ export interface CoverageOptions {
47
53
  skillsDir?: string;
48
54
  profilesDir?: string;
49
55
  floorPath?: string;
56
+ exemptionsPath?: string;
50
57
  }
51
58
 
52
59
  export interface TierCoverage {
@@ -222,6 +229,52 @@ export function writeFloor(opts: CoverageOptions = {}): CoverageReport {
222
229
  return report;
223
230
  }
224
231
 
232
+ export function _exemptionsPath(opts: CoverageOptions = {}): string {
233
+ return (
234
+ opts.exemptionsPath ??
235
+ path.join(REPO_ROOT, 'internal', 'evals', 'tier-floor-exemptions.json')
236
+ );
237
+ }
238
+
239
+ function _readExemptions(p: string): Record<string, string> {
240
+ try {
241
+ const raw = JSON.parse(fs.readFileSync(p, 'utf-8')) as {
242
+ exemptions?: Record<string, unknown>;
243
+ };
244
+ const out: Record<string, string> = {};
245
+ for (const [k, v] of Object.entries(raw.exemptions ?? {})) {
246
+ if (typeof v === 'string' && v.trim().length > 0) out[k] = v;
247
+ }
248
+ return out;
249
+ } catch {
250
+ return {};
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Tier floor: every priority-tier skill (rich + default-surface + router)
256
+ * must carry evals.json, unless exempted with a reason. Stale exemptions
257
+ * (exempted but now covered, or no longer priority) are reported so the
258
+ * list cannot silently accumulate.
259
+ */
260
+ export function checkTierFloor(opts: CoverageOptions = {}): {
261
+ ok: boolean;
262
+ violations: string[];
263
+ stale: string[];
264
+ } {
265
+ const report = computeCoverage(opts);
266
+ const exemptions = _readExemptions(_exemptionsPath(opts));
267
+ const uncovered = report.tiers.priority.uncovered;
268
+ const violations = uncovered
269
+ .filter((n) => !(n in exemptions))
270
+ .map((n) => `priority-tier skill '${n}' has no evals/evals.json and no exemption`);
271
+ const uncoveredSet = new Set(uncovered);
272
+ const stale = Object.keys(exemptions)
273
+ .filter((n) => !uncoveredSet.has(n))
274
+ .map((n) => `exemption for '${n}' is stale (covered or no longer priority-tier)`);
275
+ return { ok: violations.length === 0, violations, stale };
276
+ }
277
+
225
278
  /** Ratchet: current coverage must be >= floor, overall and per tier. */
226
279
  export function checkRatchet(opts: CoverageOptions = {}): { ok: boolean; regressions: string[] } {
227
280
  const report = computeCoverage(opts);
@@ -281,12 +334,27 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
281
334
 
282
335
  if (argv.includes('--check')) {
283
336
  const { ok, regressions } = checkRatchet();
284
- if (!ok) {
285
- process.stdout.write('❌ eval-coverage ratchet regressed:\n');
286
- for (const r of regressions) process.stdout.write(` - ${r}\n`);
337
+ const floor = checkTierFloor();
338
+ if (!ok || !floor.ok) {
339
+ if (!ok) {
340
+ process.stdout.write('❌ eval-coverage ratchet regressed:\n');
341
+ for (const r of regressions) process.stdout.write(` - ${r}\n`);
342
+ }
343
+ if (!floor.ok) {
344
+ process.stdout.write('❌ priority-tier eval floor violated:\n');
345
+ for (const v of floor.violations) process.stdout.write(` - ${v}\n`);
346
+ process.stdout.write(
347
+ ' Author a behavioural evals.json, or add an exemption WITH a reason\n' +
348
+ ' to internal/evals/tier-floor-exemptions.json.\n',
349
+ );
350
+ }
287
351
  return 1;
288
352
  }
353
+ for (const s of floor.stale) {
354
+ process.stdout.write(`⚠️ ${s}\n`);
355
+ }
289
356
  process.stdout.write('✅ eval-coverage ratchet: no regression.\n');
357
+ process.stdout.write('✅ priority-tier eval floor: every rich/default-surface/router skill covered or exempted-with-reason.\n');
290
358
  return 0;
291
359
  }
292
360
 
@@ -1416,6 +1416,7 @@ export function lint_skill(p: string, text: string): LintResult {
1416
1416
  'flux',
1417
1417
  'forecasting',
1418
1418
  'grafana',
1419
+ 'humanizer',
1419
1420
  'iconography',
1420
1421
  'laravel',
1421
1422
  'livewire',