@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/AGENTS.md +1 -0
- package/CHANGELOG.md +67 -149
- package/README.md +6 -6
- package/dist/agent-src/commands/challenge-me/vision.md +121 -32
- package/dist/agent-src/commands/challenge-me/with-docs.md +21 -8
- package/dist/agent-src/commands/commit/in-chunks.md +1 -1
- package/dist/agent-src/commands/commit.md +2 -1
- package/dist/agent-src/commands/cost/report.md +2 -1
- package/dist/agent-src/commands/feature/dev.md +4 -4
- package/dist/agent-src/commands/humanize.md +101 -0
- package/dist/agent-src/commands/jira-ticket.md +2 -2
- package/dist/agent-src/commands/memory/load.md +15 -0
- package/dist/agent-src/commands/optimize/project.md +12 -0
- package/dist/agent-src/commands/roadmap/create.md +12 -11
- package/dist/agent-src/commands/tests/create.md +1 -1
- package/dist/agent-src/commands/worktree/cleanup.md +22 -17
- package/dist/agent-src/commands/worktree/create.md +3 -1
- package/dist/agent-src/commands/worktree/status.md +12 -0
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +66 -0
- package/dist/agent-src/contexts/execution/orchestration-benchmark-gate.md +1 -0
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +47 -3
- package/dist/agent-src/contexts/execution/roadmap-ci-steps-mechanics.md +117 -0
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +111 -85
- package/dist/agent-src/contexts/execution/roadmap-writing-source-derived.md +65 -0
- package/dist/agent-src/contexts/execution/subagent-modes-detail.md +84 -0
- package/dist/agent-src/rules/architecture.md +6 -51
- package/dist/agent-src/rules/autonomous-execution.md +7 -37
- package/dist/agent-src/rules/broken-access-control.md +7 -45
- package/dist/agent-src/rules/code-comment-discipline.md +64 -0
- package/dist/agent-src/rules/context-hygiene.md +14 -0
- package/dist/agent-src/rules/decision-revisit-gate.md +9 -77
- package/dist/agent-src/rules/delegation-policy.md +10 -8
- package/dist/agent-src/rules/design-fidelity.md +4 -0
- package/dist/agent-src/rules/git-history-discipline.md +4 -79
- package/dist/agent-src/rules/improve-before-implement.md +7 -66
- package/dist/agent-src/rules/legal-safety-floor.md +11 -55
- package/dist/agent-src/rules/no-roadmap-references.md +13 -77
- package/dist/agent-src/rules/persona-governance.md +5 -47
- package/dist/agent-src/rules/provider-lifecycle-discipline.md +6 -32
- package/dist/agent-src/rules/roadmap-ci-steps-policy.md +21 -104
- package/dist/agent-src/rules/roadmap-progress-sync.md +5 -56
- package/dist/agent-src/rules/think-before-action.md +10 -0
- package/dist/agent-src/rules/untrusted-input-defense.md +3 -75
- package/dist/agent-src/rules/verify-before-complete.md +12 -0
- package/dist/agent-src/skills/agent-docs-writing/SKILL.md +96 -12
- package/dist/agent-src/skills/ai-council/SKILL.md +19 -9
- package/dist/agent-src/skills/api-design/SKILL.md +7 -5
- package/dist/agent-src/skills/api-testing/SKILL.md +1 -1
- package/dist/agent-src/skills/authz-review/SKILL.md +59 -2
- package/dist/agent-src/skills/blast-radius-analyzer/SKILL.md +1 -1
- package/dist/agent-src/skills/bug-analyzer/SKILL.md +19 -16
- package/dist/agent-src/skills/code-refactoring/SKILL.md +9 -9
- package/dist/agent-src/skills/content-funnel-design/SKILL.md +2 -0
- package/dist/agent-src/skills/data-flow-mapper/SKILL.md +1 -1
- package/dist/agent-src/skills/decision-review/SKILL.md +86 -0
- package/dist/agent-src/skills/defense-in-depth/SKILL.md +2 -2
- package/dist/agent-src/skills/dependency-upgrade/SKILL.md +7 -7
- package/dist/agent-src/skills/design-intelligence/SKILL.md +8 -8
- package/dist/agent-src/skills/design-review/SKILL.md +23 -2
- package/dist/agent-src/skills/doc-coauthoring/SKILL.md +2 -0
- package/dist/agent-src/skills/editorial-calendar/SKILL.md +2 -0
- package/dist/agent-src/skills/fe-design/SKILL.md +7 -9
- package/dist/agent-src/skills/git-workflow/SKILL.md +86 -0
- package/dist/agent-src/skills/humanizer/SKILL.md +147 -0
- package/dist/agent-src/skills/humanizer/data/patterns.md +190 -0
- package/dist/agent-src/skills/humanizer/evals/triggers.json +17 -0
- package/dist/agent-src/skills/laravel/SKILL.md +14 -1
- package/dist/agent-src/skills/launch-readiness/SKILL.md +20 -19
- package/dist/agent-src/skills/legal-practice-profile/SKILL.md +126 -0
- package/dist/agent-src/skills/logging-monitoring/SKILL.md +1 -1
- package/dist/agent-src/skills/mcp/SKILL.md +1 -1
- package/dist/agent-src/skills/memory-consolidation/SKILL.md +14 -1
- package/dist/agent-src/skills/migration-architect/SKILL.md +2 -1
- package/dist/agent-src/skills/module-detect-on-the-fly/SKILL.md +58 -1
- package/dist/agent-src/skills/performance/SKILL.md +1 -1
- package/dist/agent-src/skills/php-debugging/SKILL.md +17 -0
- package/dist/agent-src/skills/readme-writing/SKILL.md +2 -0
- package/dist/agent-src/skills/readme-writing-package/SKILL.md +2 -0
- package/dist/agent-src/skills/release-comms/SKILL.md +2 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +22 -51
- package/dist/agent-src/skills/security-audit/SKILL.md +6 -9
- package/dist/agent-src/skills/sentry-integration/SKILL.md +2 -2
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +8 -0
- package/dist/agent-src/skills/source-discovery/SKILL.md +26 -24
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +55 -46
- package/dist/agent-src/skills/subagent-orchestration/evals/evals.json +42 -0
- package/dist/agent-src/skills/systematic-debugging/SKILL.md +10 -0
- package/dist/agent-src/skills/using-git-worktrees/SKILL.md +10 -0
- package/dist/agent-src/skills/worktree-lifecycle/SKILL.md +27 -20
- package/dist/agent-src/skills/worktree-lifecycle/evals/evals.json +38 -0
- package/dist/agent-src/templates/agent-settings.md +3 -2
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +15 -1
- package/dist/agent-src/templates/contexts/knowledge-card.md +12 -0
- package/dist/agent-src/templates/roadmaps.md +14 -0
- package/dist/cli/commands/recordTriggerEval.js +4 -14
- package/dist/cli/commands/recordTriggerEval.js.map +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +157 -88
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +6 -6
- 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 +14 -9
- package/dist/install/install.mjs +12 -7
- package/dist/mcp/registry-manifest.json +4 -4
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/claude_settings_hooks.js +8 -0
- package/dist/scripts/_lib/claude_settings_hooks.js.map +1 -1
- package/dist/scripts/_lib/trigger_eval_floors.js +24 -0
- package/dist/scripts/_lib/trigger_eval_floors.js.map +1 -0
- package/dist/scripts/condense.js +12 -2
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/schemas/settings.js +5 -2
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/CLAIMS.md +54 -5
- package/docs/architecture.md +4 -4
- package/docs/archive/CHANGELOG-pre-8.12.0.md +304 -0
- package/docs/benchmark.md +90 -0
- package/docs/catalog.md +8 -5
- package/docs/command-flows.md +2 -1
- package/docs/contracts/ai-council-config.md +97 -0
- package/docs/contracts/command-clusters.md +1 -0
- package/docs/contracts/evidence-based-pruning.md +1 -0
- package/docs/contracts/hook-architecture-v1.md +25 -0
- package/docs/contracts/kernel-membership.md +35 -0
- package/docs/contracts/measurement-baseline.md +1 -0
- package/docs/contracts/persona-schema.md +49 -0
- package/docs/contracts/provider-lifecycle.md +29 -4
- package/docs/contracts/release-sizing.md +22 -0
- package/docs/contracts/write-engine.md +38 -1
- package/docs/customization.md +12 -0
- package/docs/decisions/ADR-002-kernel-bucket-overrides.md +11 -0
- package/docs/decisions/ADR-003-flat-cluster-subs-and-colon-syntax.md +11 -0
- package/docs/decisions/ADR-006-skill-tools-python-pilot.md +1 -1
- package/docs/decisions/ADR-028-root-layout.md +1 -1
- package/docs/decisions/ADR-038-canonical-settings-path.md +10 -0
- package/docs/decisions/ADR-092-defer-command-tier-alias-removal.md +29 -5
- package/docs/decisions/ADR-093-ai-council-config-user-global.md +1 -1
- package/docs/decisions/ADR-100-global-knowledge-card-sharing.md +7 -0
- package/docs/decisions/ADR-103-global-knowledge-default-off-until-measured.md +2 -2
- package/docs/decisions/ADR-105-automatic-subagent-orchestration.md +1 -1
- package/docs/decisions/ADR-114-commit-policy-iron-law-override.md +11 -0
- package/docs/decisions/ADR-116-memory-tripwire-activation-path.md +11 -1
- package/docs/decisions/ADR-117-subagents-auto-default-on.md +2 -1
- package/docs/decisions/ADR-118-loop-engineering-boundaries.md +162 -0
- package/docs/decisions/ADR-119-global-knowledge-default-on.md +104 -0
- package/docs/decisions/ADR-120-council-chairman-mode.md +70 -0
- package/docs/decisions/ADR-121-knowledge-sensitivity-classes.md +90 -0
- package/docs/decisions/ADR-200-python-to-typescript-migration.md +11 -1
- package/docs/decisions/ADR-rule-kernel-and-router.md +11 -0
- package/docs/decisions/INDEX.md +13 -9
- package/docs/design/council-vs-solo-baseline.md +92 -0
- package/docs/design/cross-model-parity-eval.md +82 -0
- package/docs/design/length-neutral-judge-rerun.md +92 -0
- package/docs/design/utilization-window-criteria.md +51 -0
- package/docs/featured-skills.md +2 -2
- package/docs/getting-started-by-role.md +1 -1
- package/docs/getting-started.md +2 -2
- package/docs/governance-advantage.md +1 -1
- package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +68 -0
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +80 -5
- package/docs/guidelines/agent-infra/rule-body-migration-inventory.md +241 -0
- package/docs/guidelines/agent-infra/untrusted-input-spotlighting.md +80 -3
- package/docs/guidelines/code-clarity.md +191 -1
- package/docs/guidelines/design-antipatterns.md +20 -0
- package/docs/guidelines/php/php-coding-patterns.md +12 -2
- package/docs/maintainers/branch-protection.md +87 -0
- package/docs/maintainers/system-map.md +85 -0
- package/docs/proof.md +32 -15
- package/docs/wedge/production-validator/production-validator.md +27 -0
- package/package.json +1 -1
- package/src/config/agent-settings.template.yml +38 -9
- package/src/scripts/_cli/cmd_doctor.ts +129 -1
- package/src/scripts/_lib/auto_dispatch.corpus.test.ts +87 -0
- package/src/scripts/_lib/claude_settings_hooks.ts +8 -0
- package/src/scripts/_lib/knowledge_global.ts +50 -2
- package/src/scripts/_lib/knowledge_global_promote.ts +150 -0
- package/src/scripts/_lib/knowledge_global_redaction.ts +42 -4
- package/src/scripts/_lib/lexical_index.ts +15 -2
- package/src/scripts/_lib/orchestration_record.ts +14 -0
- package/src/scripts/_lib/trigger_eval_floors.ts +31 -0
- package/src/scripts/ai_council/chairman.ts +90 -0
- package/src/scripts/ai_council/clients.ts +18 -3
- package/src/scripts/ai_council/config.ts +157 -0
- package/src/scripts/ai_council/debate_gates.ts +89 -0
- package/src/scripts/ai_council/orchestrator.ts +252 -2
- package/src/scripts/ai_council/prompts.ts +121 -11
- package/src/scripts/ai_council/replay.ts +17 -0
- package/src/scripts/ai_council/stance_tally.ts +184 -0
- package/src/scripts/ai_tells_rules.ts +433 -0
- package/src/scripts/bench_humanizer_eval.ts +240 -0
- package/src/scripts/bench_persona_placebo.ts +308 -0
- package/src/scripts/bench_quality_rerun.ts +357 -0
- package/src/scripts/build_discovery_manifest.ts +6 -0
- package/src/scripts/build_proof.ts +30 -9
- package/src/scripts/check_artefact_count_messaging.ts +56 -0
- package/src/scripts/check_claims.ts +40 -0
- package/src/scripts/check_knowledge_cards.ts +33 -4
- package/src/scripts/check_portability.ts +1 -0
- package/src/scripts/check_rule_invariants.ts +10 -3
- package/src/scripts/complexity_report.ts +776 -0
- package/src/scripts/condense.ts +16 -2
- package/src/scripts/consumer_matrix.ts +292 -14
- package/src/scripts/council_cli.ts +203 -1
- package/src/scripts/detect_ai_tells.ts +312 -0
- package/src/scripts/explain_run.ts +593 -0
- package/src/scripts/install.ts +12 -0
- package/src/scripts/knowledge_global_cli.ts +235 -46
- package/src/scripts/lint_framework_leakage.ts +88 -7
- package/src/scripts/lint_framework_leakage_allowlist.json +59 -572
- package/src/scripts/lint_knowledge_scale.ts +30 -0
- package/src/scripts/lint_originality_shingles.ts +210 -0
- package/src/scripts/orchestration_record.ts +1 -0
- package/src/scripts/rule_backlinks.ts +0 -0
- package/src/scripts/schemas/agent-settings.schema.json +11 -0
- package/src/scripts/self_review_gate.ts +41 -15
- package/src/scripts/skill_eval_coverage.ts +71 -3
- package/src/scripts/skill_linter.ts +1 -0
- package/src/scripts/trigger_eval_rotation.ts +344 -0
- package/src/scripts/update_counts.ts +5 -17
- package/src/scripts/worktree_cleanup_check.ts +263 -0
- package/src/server/schemas/settings.ts +8 -3
- package/src/scripts/analysis_freshness.ts +0 -362
- 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
|
-
'::
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
|