@event4u/agent-config 8.9.0 → 8.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +189 -0
- package/README.md +5 -5
- 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 +14 -3
- 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 +29 -7
- package/dist/agent-src/commands/work.md +33 -0
- package/dist/agent-src/commands/worktree/cleanup.md +83 -0
- package/dist/agent-src/commands/worktree/create.md +64 -0
- package/dist/agent-src/commands/worktree/status.md +75 -0
- package/dist/agent-src/commands/worktree/verify.md +70 -0
- package/dist/agent-src/commands/worktree.md +85 -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 +91 -34
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +14 -0
- 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/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 +148 -0
- package/dist/agent-src/rules/context-hygiene.md +14 -0
- package/dist/agent-src/rules/delegation-policy.md +10 -8
- package/dist/agent-src/rules/design-fidelity.md +44 -0
- package/dist/agent-src/rules/git-history-discipline.md +4 -79
- package/dist/agent-src/rules/legal-safety-floor.md +11 -55
- package/dist/agent-src/rules/roadmap-progress-sync.md +5 -56
- package/dist/agent-src/rules/spreadsheet-source-quality.md +61 -0
- package/dist/agent-src/rules/think-before-action.md +10 -0
- package/dist/agent-src/rules/verify-before-complete.md +12 -0
- package/dist/agent-src/skills/accessibility-auditor/evals/evals.json +15 -0
- package/dist/agent-src/skills/agent-docs-writing/SKILL.md +13 -12
- package/dist/agent-src/skills/ai-council/SKILL.md +19 -9
- package/dist/agent-src/skills/analysis-skill-router/evals/evals.json +13 -0
- package/dist/agent-src/skills/api-design/SKILL.md +7 -5
- package/dist/agent-src/skills/api-testing/SKILL.md +16 -2
- 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/character-consistency/evals/evals.json +16 -0
- package/dist/agent-src/skills/code-refactoring/SKILL.md +9 -9
- package/dist/agent-src/skills/command-routing/evals/evals.json +14 -0
- package/dist/agent-src/skills/content-funnel-design/SKILL.md +2 -0
- package/dist/agent-src/skills/dashboard-design/evals/evals.json +16 -0
- package/dist/agent-src/skills/data-flow-mapper/SKILL.md +1 -1
- package/dist/agent-src/skills/dcf-modeling/evals/evals.json +20 -0
- package/dist/agent-src/skills/decision-record/evals/evals.json +20 -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-intelligence/evals/evals.json +14 -0
- package/dist/agent-src/skills/design-review/SKILL.md +29 -2
- package/dist/agent-src/skills/design-system-capture/SKILL.md +21 -0
- package/dist/agent-src/skills/design-system-capture/evals/evals.json +15 -0
- package/dist/agent-src/skills/developer-like-execution/evals/evals.json +16 -0
- package/dist/agent-src/skills/doc-coauthoring/SKILL.md +29 -0
- package/dist/agent-src/skills/doc-coauthoring/evals/evals.json +16 -0
- package/dist/agent-src/skills/editorial-calendar/SKILL.md +2 -0
- package/dist/agent-src/skills/editorial-calendar/evals/domain-truth.json +16 -0
- package/dist/agent-src/skills/editorial-calendar/evals/evals.json +16 -0
- package/dist/agent-src/skills/estimate-ticket/evals/evals.json +16 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +30 -0
- package/dist/agent-src/skills/existing-ui-audit/evals/triggers.json +47 -0
- package/dist/agent-src/skills/fe-design/SKILL.md +43 -14
- package/dist/agent-src/skills/fe-design/evals/triggers.json +47 -0
- package/dist/agent-src/skills/forecasting/evals/evals.json +20 -0
- package/dist/agent-src/skills/fundraising-narrative/evals/domain-truth.json +16 -0
- package/dist/agent-src/skills/fundraising-narrative/evals/evals.json +16 -0
- package/dist/agent-src/skills/git-workflow/SKILL.md +86 -0
- package/dist/agent-src/skills/html-deck/SKILL.md +27 -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/iconography/SKILL.md +24 -0
- package/dist/agent-src/skills/incident-commander/evals/domain-truth.json +16 -0
- package/dist/agent-src/skills/incident-commander/evals/evals.json +20 -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/launch-readiness/evals/evals.json +21 -0
- 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/logging-monitoring/evals/evals.json +16 -0
- 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/messaging-architecture/evals/domain-truth.json +16 -0
- package/dist/agent-src/skills/messaging-architecture/evals/evals.json +16 -0
- package/dist/agent-src/skills/migration-architect/SKILL.md +2 -1
- package/dist/agent-src/skills/perf-feedback-craft/evals/evals.json +16 -0
- package/dist/agent-src/skills/performance/SKILL.md +1 -1
- package/dist/agent-src/skills/pest-testing/SKILL.md +1 -1
- package/dist/agent-src/skills/php-debugging/SKILL.md +17 -0
- package/dist/agent-src/skills/playwright-testing/SKILL.md +16 -0
- package/dist/agent-src/skills/prompt-engineering-patterns/evals/evals.json +16 -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/refine-prompt/evals/evals.json +16 -0
- package/dist/agent-src/skills/refine-ticket/evals/evals.json +16 -0
- package/dist/agent-src/skills/release-comms/SKILL.md +2 -0
- package/dist/agent-src/skills/release-comms/evals/evals.json +16 -0
- package/dist/agent-src/skills/rice-prioritization/evals/evals.json +20 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +22 -51
- package/dist/agent-src/skills/runway-cognition/evals/evals.json +20 -0
- package/dist/agent-src/skills/scenario-modeling/evals/evals.json +20 -0
- 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/spreadsheet-authoring/SKILL.md +98 -0
- package/dist/agent-src/skills/spreadsheet-authoring/evals/triggers.json +47 -0
- 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/systematic-debugging/evals/evals.json +20 -0
- package/dist/agent-src/skills/test-case-discovery/SKILL.md +118 -0
- package/dist/agent-src/skills/test-case-discovery/evals/triggers.json +52 -0
- package/dist/agent-src/skills/test-driven-development/SKILL.md +32 -6
- package/dist/agent-src/skills/test-driven-development/evals/evals.json +16 -0
- package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +7 -6
- package/dist/agent-src/skills/threat-modeling/evals/evals.json +21 -0
- package/dist/agent-src/skills/typography-system/evals/evals.json +14 -0
- package/dist/agent-src/skills/unit-economics-modeling/evals/evals.json +24 -0
- package/dist/agent-src/skills/using-git-worktrees/SKILL.md +10 -0
- package/dist/agent-src/skills/verify-completion-evidence/evals/evals.json +16 -0
- package/dist/agent-src/skills/vision-articulation/evals/evals.json +16 -0
- package/dist/agent-src/skills/voice-and-tone-design/evals/evals.json +16 -0
- package/dist/agent-src/skills/worktree-lifecycle/SKILL.md +187 -0
- package/dist/agent-src/skills/worktree-lifecycle/evals/evals.json +38 -0
- package/dist/agent-src/skills/worktree-lifecycle/evals/triggers.json +52 -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/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 +374 -88
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +9 -9
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +30 -18
- package/dist/discovery/trust-report.md +5 -5
- package/dist/discovery/workspaces.json +31 -15
- 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 +24 -3
- package/docs/architecture.md +3 -3
- package/docs/benchmark.md +51 -0
- package/docs/catalog.md +23 -9
- package/docs/command-flows.md +7 -1
- package/docs/contracts/CHANGELOG-conventions.md +6 -0
- package/docs/contracts/ai-council-config.md +92 -0
- package/docs/contracts/command-clusters.md +1 -0
- package/docs/contracts/design-artifact-lifecycle.md +93 -0
- package/docs/contracts/design-artifact-verification.md +30 -1
- 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/release-pr-gating.md +26 -0
- package/docs/contracts/release-sizing.md +107 -0
- package/docs/contracts/surface-agent-contracts.md +153 -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-115-command-cluster-consolidation-phase-4.md +1 -1
- 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-200-python-to-typescript-migration.md +11 -1
- package/docs/decisions/ADR-rule-kernel-and-router.md +11 -0
- package/docs/decisions/INDEX.md +11 -9
- package/docs/design/cross-model-parity-eval.md +82 -0
- package/docs/design/length-neutral-judge-rerun.md +92 -0
- package/docs/distribution/consumer-matrix.md +96 -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/roadmap-progress-mechanics.md +80 -5
- package/docs/guidelines/code-clarity.md +106 -1
- package/docs/guidelines/design-antipatterns.md +20 -0
- package/docs/guidelines/php/php-coding-patterns.md +12 -2
- package/docs/proof.md +21 -15
- package/docs/release-runbook.md +3 -0
- package/docs/self-review-gate.md +51 -0
- package/docs/wedge/production-validator/production-validator.md +27 -0
- package/package.json +1 -1
- package/src/config/agent-settings.template.yml +29 -9
- package/src/scripts/_cli/cmd_doctor.ts +34 -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 +6 -2
- 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 +27 -0
- package/src/scripts/_lib/orchestration_savings.ts +52 -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 +65 -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/build_discovery_manifest.ts +6 -0
- package/src/scripts/build_proof.ts +21 -9
- package/src/scripts/check_claims.ts +40 -0
- package/src/scripts/check_release_adjacent_health.ts +121 -0
- package/src/scripts/check_rule_invariants.ts +282 -0
- package/src/scripts/condense.ts +16 -2
- package/src/scripts/consumer_matrix.ts +392 -0
- package/src/scripts/council_cli.ts +147 -0
- package/src/scripts/detect_ai_tells.ts +312 -0
- package/src/scripts/generate_index.ts +3 -2
- package/src/scripts/install.ts +12 -0
- package/src/scripts/knowledge_global_cli.ts +1 -1
- package/src/scripts/lint_changelog_rollback.ts +293 -0
- 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 +17 -0
- package/src/scripts/orchestration_savings_report.ts +36 -3
- package/src/scripts/schemas/agent-settings.schema.json +11 -0
- package/src/scripts/second_brain_retrieval.ts +44 -2
- package/src/scripts/self_review_gate.test.ts +65 -0
- package/src/scripts/self_review_gate.ts +298 -0
- package/src/scripts/skill_eval_coverage.ts +71 -3
- package/src/scripts/skill_linter.ts +1 -0
- package/src/scripts/trigger_eval_grandfather.json +223 -224
- 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
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* bench_humanizer_eval — paired eval for the humanizer feature.
|
|
4
|
+
*
|
|
5
|
+
* Two metrics over the before/after corpus in `tests/fixtures/ai-tells/`:
|
|
6
|
+
*
|
|
7
|
+
* 1. OBJECTIVE (free, deterministic): AI-tell reduction per pair via
|
|
8
|
+
* `detect_ai_tells.ts` — hard hits, cluster score/500w, dash density/500w.
|
|
9
|
+
* 2. BLIND PREFERENCE (--judge, billable): an LLM judge sees each pair in
|
|
10
|
+
* randomized A/B order (deterministic LCG seed per pair) and picks which
|
|
11
|
+
* text reads more like a skilled human writer. LENGTH-CONTROLLED: pairs
|
|
12
|
+
* whose word counts differ by more than ±25% are excluded from the
|
|
13
|
+
* preference aggregate (the token program's verbosity-bias finding makes
|
|
14
|
+
* uncontrolled pairs unusable) — they still count for the objective metric.
|
|
15
|
+
*
|
|
16
|
+
* Reports follow docs/benchmarks.md naming:
|
|
17
|
+
* internal/bench/reports/humanizer-v1.{json,md} (canonical pointer)
|
|
18
|
+
* internal/bench/reports/<ISO>-humanizer-v1.{json,md} (immutable trail)
|
|
19
|
+
*
|
|
20
|
+
* Claims discipline: this eval backs `claim:humanizer-tell-reduction` in
|
|
21
|
+
* docs/CLAIMS.md. It measures OUR pattern counts and a blind preference —
|
|
22
|
+
* never third-party "AI detector" outcomes; that claim class stays banned.
|
|
23
|
+
*
|
|
24
|
+
* Usage:
|
|
25
|
+
* npx tsx src/scripts/bench_humanizer_eval.ts # objective only (free)
|
|
26
|
+
* npx tsx src/scripts/bench_humanizer_eval.ts --judge --confirm-spend # + blind preference (billable)
|
|
27
|
+
*
|
|
28
|
+
* `--judge` without `--confirm-spend` estimates the billable call count and
|
|
29
|
+
* halts (exit 2) — the spend is opt-in, never fired implicitly.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
33
|
+
import { join } from "node:path";
|
|
34
|
+
import { analyzeText, type TellReport } from "./detect_ai_tells.js";
|
|
35
|
+
|
|
36
|
+
const ROOT = process.cwd();
|
|
37
|
+
const FIXTURES = join(ROOT, "tests", "fixtures", "ai-tells");
|
|
38
|
+
const REPORT_DIR = join(ROOT, "internal", "bench", "reports");
|
|
39
|
+
const LENGTH_TOLERANCE = 0.25;
|
|
40
|
+
|
|
41
|
+
interface PairResult {
|
|
42
|
+
name: string;
|
|
43
|
+
language: "en" | "de";
|
|
44
|
+
words_before: number;
|
|
45
|
+
words_after: number;
|
|
46
|
+
length_ratio: number;
|
|
47
|
+
length_controlled: boolean;
|
|
48
|
+
before: Pick<TellReport, "hard_total" | "cluster_score_per_500" | "dash_density_per_500">;
|
|
49
|
+
after: Pick<TellReport, "hard_total" | "cluster_score_per_500" | "dash_density_per_500">;
|
|
50
|
+
judge?: { preferred: "after" | "before"; order: "after-first" | "before-first" };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function slim(r: TellReport) {
|
|
54
|
+
return {
|
|
55
|
+
hard_total: r.hard_total,
|
|
56
|
+
cluster_score_per_500: r.cluster_score_per_500,
|
|
57
|
+
dash_density_per_500: r.dash_density_per_500,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Deterministic per-pair coin flip (LCG) so re-runs keep the same A/B order. */
|
|
62
|
+
function coin(seed: number): boolean {
|
|
63
|
+
return ((seed * 1103515245 + 12345) >>> 16) % 2 === 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function loadPairs(): Array<{ name: string; language: "en" | "de"; before: string; after: string }> {
|
|
67
|
+
const out: Array<{ name: string; language: "en" | "de"; before: string; after: string }> = [];
|
|
68
|
+
for (const language of ["en", "de"] as const) {
|
|
69
|
+
const dir = join(FIXTURES, language);
|
|
70
|
+
let names: string[] = [];
|
|
71
|
+
try {
|
|
72
|
+
names = readdirSync(dir)
|
|
73
|
+
.filter((f) => f.endsWith(".before.md"))
|
|
74
|
+
.map((f) => f.replace(/\.before\.md$/, ""))
|
|
75
|
+
.sort();
|
|
76
|
+
} catch {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
for (const name of names) {
|
|
80
|
+
out.push({
|
|
81
|
+
name,
|
|
82
|
+
language,
|
|
83
|
+
before: readFileSync(join(dir, `${name}.before.md`), "utf8"),
|
|
84
|
+
after: readFileSync(join(dir, `${name}.after.md`), "utf8"),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function judgePair(
|
|
92
|
+
pair: { name: string; before: string; after: string },
|
|
93
|
+
index: number,
|
|
94
|
+
): Promise<{ preferred: "after" | "before"; order: "after-first" | "before-first" }> {
|
|
95
|
+
const { AnthropicClient, load_anthropic_key } = await import("./ai_council/clients.js");
|
|
96
|
+
const client = new AnthropicClient({ api_key: load_anthropic_key() });
|
|
97
|
+
const afterFirst = coin(index + 1);
|
|
98
|
+
const [a, b] = afterFirst ? [pair.after, pair.before] : [pair.before, pair.after];
|
|
99
|
+
const system =
|
|
100
|
+
"You judge writing quality blind. You will see two texts, A and B, covering the same content. " +
|
|
101
|
+
"Pick the one that reads more like it was written by a skilled human writer for a real audience. " +
|
|
102
|
+
"Judge prose quality only — not length, not topic. Reply with exactly one character: A or B.";
|
|
103
|
+
const user = `Text A:\n\n${a}\n\n---\n\nText B:\n\n${b}\n\nWhich reads more like a skilled human writer? Reply A or B.`;
|
|
104
|
+
const resp = client.ask(system, user, 8);
|
|
105
|
+
const text = (resp.text ?? "").trim().toUpperCase();
|
|
106
|
+
const pickedA = text.startsWith("A");
|
|
107
|
+
const preferred = pickedA === afterFirst ? "after" : "before";
|
|
108
|
+
return { preferred, order: afterFirst ? "after-first" : "before-first" };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function main(): Promise<void> {
|
|
112
|
+
const runJudge = process.argv.includes("--judge");
|
|
113
|
+
const confirmSpend = process.argv.includes("--confirm-spend");
|
|
114
|
+
const pairs = loadPairs();
|
|
115
|
+
if (pairs.length < 20) {
|
|
116
|
+
console.error(`corpus too small: ${pairs.length} pairs (< 20)`);
|
|
117
|
+
process.exit(2);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Spend gate: --judge fires one billable API call per length-controlled
|
|
121
|
+
// pair (via the council client's curl transport). Estimate + halt unless
|
|
122
|
+
// --confirm-spend is passed — mirrors council:run's explicit-confirm model,
|
|
123
|
+
// never an interactive prompt. The objective-only default path is free.
|
|
124
|
+
if (runJudge && !confirmSpend) {
|
|
125
|
+
const controlledCount = pairs.filter((p) => {
|
|
126
|
+
const b = analyzeText(p.before, p.language).words;
|
|
127
|
+
const a = analyzeText(p.after, p.language).words;
|
|
128
|
+
return Math.abs(1 - a / b) <= LENGTH_TOLERANCE;
|
|
129
|
+
}).length;
|
|
130
|
+
process.stderr.write(
|
|
131
|
+
`--judge makes ${controlledCount} billable API call(s) (one per length-controlled pair). ` +
|
|
132
|
+
`Re-run with --confirm-spend to authorize the spend. ` +
|
|
133
|
+
`The objective-only run (no --judge) is free.\n`,
|
|
134
|
+
);
|
|
135
|
+
process.exit(2);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const results: PairResult[] = [];
|
|
139
|
+
for (let i = 0; i < pairs.length; i++) {
|
|
140
|
+
const p = pairs[i]!;
|
|
141
|
+
const before = analyzeText(p.before, p.language);
|
|
142
|
+
const after = analyzeText(p.after, p.language);
|
|
143
|
+
const ratio = after.words / before.words;
|
|
144
|
+
const controlled = Math.abs(1 - ratio) <= LENGTH_TOLERANCE;
|
|
145
|
+
const row: PairResult = {
|
|
146
|
+
name: p.name,
|
|
147
|
+
language: p.language,
|
|
148
|
+
words_before: before.words,
|
|
149
|
+
words_after: after.words,
|
|
150
|
+
length_ratio: Math.round(ratio * 100) / 100,
|
|
151
|
+
length_controlled: controlled,
|
|
152
|
+
before: slim(before),
|
|
153
|
+
after: slim(after),
|
|
154
|
+
};
|
|
155
|
+
if (runJudge && controlled) {
|
|
156
|
+
row.judge = await judgePair(p, i);
|
|
157
|
+
process.stdout.write(` judged ${p.name}: prefers ${row.judge.preferred} (${row.judge.order})\n`);
|
|
158
|
+
}
|
|
159
|
+
results.push(row);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const mean = (xs: number[]) =>
|
|
163
|
+
Math.round((xs.reduce((s, x) => s + x, 0) / Math.max(xs.length, 1)) * 100) / 100;
|
|
164
|
+
const agg = {
|
|
165
|
+
pairs: results.length,
|
|
166
|
+
length_controlled_pairs: results.filter((r) => r.length_controlled).length,
|
|
167
|
+
mean_hard_before: mean(results.map((r) => r.before.hard_total)),
|
|
168
|
+
mean_hard_after: mean(results.map((r) => r.after.hard_total)),
|
|
169
|
+
mean_cluster_before: mean(results.map((r) => r.before.cluster_score_per_500)),
|
|
170
|
+
mean_cluster_after: mean(results.map((r) => r.after.cluster_score_per_500)),
|
|
171
|
+
mean_dash_before: mean(results.map((r) => r.before.dash_density_per_500)),
|
|
172
|
+
mean_dash_after: mean(results.map((r) => r.after.dash_density_per_500)),
|
|
173
|
+
judge_model: undefined as string | undefined,
|
|
174
|
+
judged_pairs: 0,
|
|
175
|
+
prefers_after: 0,
|
|
176
|
+
prefers_before: 0,
|
|
177
|
+
};
|
|
178
|
+
const judged = results.filter((r) => r.judge);
|
|
179
|
+
agg.judged_pairs = judged.length;
|
|
180
|
+
agg.prefers_after = judged.filter((r) => r.judge!.preferred === "after").length;
|
|
181
|
+
agg.prefers_before = judged.filter((r) => r.judge!.preferred === "before").length;
|
|
182
|
+
if (runJudge) {
|
|
183
|
+
const { DEFAULT_ANTHROPIC_MODEL } = await import("./ai_council/clients.js");
|
|
184
|
+
agg.judge_model = DEFAULT_ANTHROPIC_MODEL as string;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const iso = new Date().toISOString().replace(/\.\d+Z$/, "Z").replace(/:/g, "-");
|
|
188
|
+
const payload = { generated: iso, corpus: "tests/fixtures/ai-tells", aggregate: agg, results };
|
|
189
|
+
|
|
190
|
+
const md = [
|
|
191
|
+
"# Humanizer paired eval — v1",
|
|
192
|
+
"",
|
|
193
|
+
`> Generated ${iso} · corpus \`tests/fixtures/ai-tells\` (${agg.pairs} before/after pairs, ` +
|
|
194
|
+
`${agg.length_controlled_pairs} length-controlled ±${LENGTH_TOLERANCE * 100}%).`,
|
|
195
|
+
"",
|
|
196
|
+
"## Objective — AI-tell reduction (deterministic)",
|
|
197
|
+
"",
|
|
198
|
+
"| Metric (mean) | Before | After |",
|
|
199
|
+
"|---|---|---|",
|
|
200
|
+
`| Hard hits | ${agg.mean_hard_before} | ${agg.mean_hard_after} |`,
|
|
201
|
+
`| Cluster score /500w | ${agg.mean_cluster_before} | ${agg.mean_cluster_after} |`,
|
|
202
|
+
`| Dash density /500w | ${agg.mean_dash_before} | ${agg.mean_dash_after} |`,
|
|
203
|
+
"",
|
|
204
|
+
"## Blind preference (length-controlled)",
|
|
205
|
+
"",
|
|
206
|
+
agg.judged_pairs > 0
|
|
207
|
+
? `Judge ${agg.judge_model}: prefers the humanized text in **${agg.prefers_after}/${agg.judged_pairs}** ` +
|
|
208
|
+
`pairs (randomized A/B order, deterministic seed). An honest null here keeps the detector as a ` +
|
|
209
|
+
`hygiene gate; the claim ledger only carries what this table shows.`
|
|
210
|
+
: "_Not run (objective-only invocation)._",
|
|
211
|
+
"",
|
|
212
|
+
"## Scope note",
|
|
213
|
+
"",
|
|
214
|
+
"This eval measures the package's own pattern counts and a blind prose-quality preference.",
|
|
215
|
+
'It never measures third-party "AI detector" outcomes — that claim class is banned',
|
|
216
|
+
"(unfalsifiable from our side; see roadmap non-goals).",
|
|
217
|
+
"",
|
|
218
|
+
"## Open question — real-draft lift is unmeasured",
|
|
219
|
+
"",
|
|
220
|
+
"The `before` fixtures were **deliberately tell-seeded**, so a perfect score",
|
|
221
|
+
"measures seeded-tell removal on a self-constructed corpus — NOT that real",
|
|
222
|
+
"ghostwriter drafts get better. Real-world lift stays **unmeasured** until",
|
|
223
|
+
"write-engine step 4b has processed real `/ghostwriter:write` drafts and those",
|
|
224
|
+
"have been paired-evaluated (the `road-to-humanizer-hardening` live-usage",
|
|
225
|
+
"blocker). The claim ledger is scoped to \"on the fixture corpus\" accordingly.",
|
|
226
|
+
"",
|
|
227
|
+
].join("\n");
|
|
228
|
+
|
|
229
|
+
mkdirSync(REPORT_DIR, { recursive: true });
|
|
230
|
+
writeFileSync(join(REPORT_DIR, "humanizer-v1.json"), JSON.stringify(payload, null, 2));
|
|
231
|
+
writeFileSync(join(REPORT_DIR, "humanizer-v1.md"), md);
|
|
232
|
+
writeFileSync(join(REPORT_DIR, `${iso}-humanizer-v1.json`), JSON.stringify(payload, null, 2));
|
|
233
|
+
writeFileSync(join(REPORT_DIR, `${iso}-humanizer-v1.md`), md);
|
|
234
|
+
process.stdout.write(md + "\n");
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
main().catch((err) => {
|
|
238
|
+
console.error(String(err));
|
|
239
|
+
process.exit(2);
|
|
240
|
+
});
|
|
@@ -419,6 +419,12 @@ function* _iter_artefacts(): Generator<[string, string]> {
|
|
|
419
419
|
// `_classify` subagent branch derives the manifest payload (default-off,
|
|
420
420
|
// packs:[]) — static projection only, never runtime-dispatched.
|
|
421
421
|
for (const p of _collect('subagents', '*.md')) {
|
|
422
|
+
// `_`-prefixed files are injected partials (e.g. _prompt-defense.md),
|
|
423
|
+
// not projectable agents — never discovery artefacts. Mirrors the
|
|
424
|
+
// same filter in condense.generate_claude_subagents.
|
|
425
|
+
if (path.basename(p).startsWith('_')) {
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
422
428
|
yield [p, 'subagent'];
|
|
423
429
|
}
|
|
424
430
|
}
|
|
@@ -27,6 +27,7 @@ import { REPO, LEDGER_REL, load_ledger, pointer_unresolved } from './check_claim
|
|
|
27
27
|
import { collectSkillGaps } from './check_skill_gaps.js';
|
|
28
28
|
import { loadRows as loadComparisonRows } from './check_comparison.js';
|
|
29
29
|
import { computeStatus as domainSoundnessStatus } from './domain_soundness_status.js';
|
|
30
|
+
import { computeCoverage } from './skill_eval_coverage.js';
|
|
30
31
|
|
|
31
32
|
const _FILE = fileURLToPath(import.meta.url);
|
|
32
33
|
const OUT_REL = 'docs/proof.md';
|
|
@@ -108,16 +109,27 @@ function render(): string {
|
|
|
108
109
|
L.push('Benchmark results are published under `docs/` including measured nulls.');
|
|
109
110
|
}
|
|
110
111
|
L.push('');
|
|
112
|
+
const cov = computeCoverage();
|
|
113
|
+
const prio = cov.tiers.priority;
|
|
114
|
+
const other = cov.tiers.other;
|
|
115
|
+
const prioLine =
|
|
116
|
+
prio.covered === prio.total
|
|
117
|
+
? `\`rich\` + routers) are **fully covered (${prio.covered} of ${prio.total})**, the long tail`
|
|
118
|
+
: `\`rich\` + routers, **${prio.covered} of ${prio.total}**) are partially covered; the long tail`;
|
|
111
119
|
L.push('**Behavioural-eval coverage — the honest baseline.** Skill *quality* is only');
|
|
112
|
-
L.push(
|
|
113
|
-
L.push('`evals.json
|
|
114
|
-
L.push(
|
|
115
|
-
L.push(
|
|
116
|
-
L.push(
|
|
117
|
-
L.push('
|
|
118
|
-
L.push('
|
|
119
|
-
L.push('
|
|
120
|
-
L.push('
|
|
120
|
+
L.push(`as good as its measurement. Today **${cov.overall.covered} of ${cov.overall.total}** skills carry a behavioural`);
|
|
121
|
+
L.push('`evals.json`; the highest-traffic / highest-cost tiers (default-surface +');
|
|
122
|
+
L.push(prioLine);
|
|
123
|
+
L.push(`(${other.covered} of ${other.total}) is not. We publish that gap rather than imply`);
|
|
124
|
+
L.push(`"${cov.overall.total} evaluated skills": coverage is measured per tier`);
|
|
125
|
+
L.push('(`./scripts-run src/scripts/skill_eval_coverage`), **CI-ratcheted so it can');
|
|
126
|
+
L.push('only rise**, and the priority tiers carry a hard **tier floor**: every');
|
|
127
|
+
L.push('rich / default-surface / router skill MUST have a behavioural eval or an');
|
|
128
|
+
L.push('explicit exemption-with-reason in `internal/evals/tier-floor-exemptions.json`');
|
|
129
|
+
L.push('— no silent exemptions (the bright line deliberately replaces a');
|
|
130
|
+
L.push('weighted-coverage score). Authoring long-tail evals is gated on per-case');
|
|
131
|
+
L.push('human ratification (a generated assertion that checks the wrong property is');
|
|
132
|
+
L.push('worse than none), so the number grows deliberately, not overnight.');
|
|
121
133
|
L.push('');
|
|
122
134
|
const ds = domainSoundnessStatus();
|
|
123
135
|
L.push('**Non-coding domain soundness — scoped, not proven.** The `finance` /');
|
|
@@ -42,6 +42,13 @@ const SURFACE_ROOTS = ['README.md', 'docs'];
|
|
|
42
42
|
const CLAIM_MARKER = /<!--\s*claim:([A-Za-z0-9._-]+)\s*-->/g;
|
|
43
43
|
const URL_DATED = /^https?:\/\/\S+\(\d{4}-\d{2}-\d{2}\)\s*$/;
|
|
44
44
|
|
|
45
|
+
/** Witness sweep (P1.2) — marketing-class surfaces where a bare number must
|
|
46
|
+
* not appear as a capability claim. docs/proof.md and docs/comparison.yaml
|
|
47
|
+
* are structurally witnessed (pointer columns + their own linters). */
|
|
48
|
+
const WITNESS_SURFACES = ['README.md', 'CAPABILITIES.yaml'];
|
|
49
|
+
/** A percentage or multiplier reading as a measured capability figure. */
|
|
50
|
+
const QUANTIFIED_CLAIM = /\b\d+(?:\.\d+)?\s*(?:%|[x×](?![A-Za-z0-9]))/;
|
|
51
|
+
|
|
45
52
|
class ExitCode extends Error {
|
|
46
53
|
code: number;
|
|
47
54
|
constructor(code: number) {
|
|
@@ -224,6 +231,39 @@ function main(argv: string[] = process.argv.slice(2)): number {
|
|
|
224
231
|
if (un) findings.push({ id: entry.id, file: LEDGER_REL, reason: `backed entry has dangling evidence — ${un}` });
|
|
225
232
|
}
|
|
226
233
|
|
|
234
|
+
// 3. Witness sweep (road-to-opt-subagent-harvest P1.2): a QUANTIFIED
|
|
235
|
+
// capability claim on a marketing-class surface must be witnessed — a
|
|
236
|
+
// `<!-- claim:ID -->` marker on the same line, or an explicit
|
|
237
|
+
// `unverified` annotation. Unmarkered numbers are exactly how an
|
|
238
|
+
// "84.8%"-style figure spreads across surfaces with no methodology
|
|
239
|
+
// (the anti-lesson this check encodes). Fenced code blocks are skipped.
|
|
240
|
+
for (const rel of WITNESS_SURFACES) {
|
|
241
|
+
const abs = path.join(REPO, rel);
|
|
242
|
+
let text: string;
|
|
243
|
+
try {
|
|
244
|
+
text = fs.readFileSync(abs, 'utf-8');
|
|
245
|
+
} catch {
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
let inFence = false;
|
|
249
|
+
const lines = text.split('\n');
|
|
250
|
+
for (let i = 0; i < lines.length; i++) {
|
|
251
|
+
const line = lines[i] as string;
|
|
252
|
+
if (/^\s*(```|~~~)/.test(line)) {
|
|
253
|
+
inFence = !inFence;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (inFence) continue;
|
|
257
|
+
if (!QUANTIFIED_CLAIM.test(line)) continue;
|
|
258
|
+
if (/<!--\s*claim:/.test(line) || /unverified/i.test(line)) continue;
|
|
259
|
+
findings.push({
|
|
260
|
+
id: '(unmarkered)',
|
|
261
|
+
file: `${rel}:${i + 1}`,
|
|
262
|
+
reason: `quantified claim without a claim marker or 'unverified' annotation — ${line.trim().slice(0, 70)}`,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
227
267
|
if (findings.length > 0) {
|
|
228
268
|
process.stderr.write(`❌ check_claims: ${findings.length} unbacked/dangling claim(s):\n`);
|
|
229
269
|
for (const f of findings) {
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Release-adjacent workflow health tripwire — road-to-proof-under-real-conditions
|
|
4
|
+
* Phase 1. The "red for five releases without anyone noticing" detector:
|
|
5
|
+
* the MCP worker deploy failed on every release from one minor line to the
|
|
6
|
+
* next and nothing surfaced it, because post-tag workflows are nobody's PR.
|
|
7
|
+
*
|
|
8
|
+
* For each watched workflow, look at its most recent COMPLETED run on the
|
|
9
|
+
* default branch (any trigger). If that run's conclusion is not `success`
|
|
10
|
+
* and it finished more than --max-red-hours ago (default 48) with no newer
|
|
11
|
+
* successful run, the workflow counts as durably red → exit 1 and print a
|
|
12
|
+
* GitHub error annotation per offender. The calling workflow turns that
|
|
13
|
+
* into a visible failure on main.
|
|
14
|
+
*
|
|
15
|
+
* Auth: GITHUB_TOKEN (or GH_TOKEN) env. Repo: GITHUB_REPOSITORY env or
|
|
16
|
+
* --repo owner/name.
|
|
17
|
+
*
|
|
18
|
+
* Exit codes: 0 all green/absent · 1 durably-red workflow(s) · 2 usage/env.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { argv, env, exit } from 'node:process';
|
|
22
|
+
|
|
23
|
+
const WATCHED = [
|
|
24
|
+
'publish-npm.yml',
|
|
25
|
+
'deploy-mcp-worker.yml',
|
|
26
|
+
'deploy-site.yml',
|
|
27
|
+
'site.yml',
|
|
28
|
+
'consumer-matrix.yml',
|
|
29
|
+
'release.yml',
|
|
30
|
+
] as const;
|
|
31
|
+
|
|
32
|
+
interface RunInfo {
|
|
33
|
+
conclusion: string | null;
|
|
34
|
+
updated_at: string;
|
|
35
|
+
html_url: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function latestCompletedRun(
|
|
39
|
+
repo: string,
|
|
40
|
+
token: string,
|
|
41
|
+
workflow: string,
|
|
42
|
+
branch: string,
|
|
43
|
+
): Promise<RunInfo | null> {
|
|
44
|
+
const url =
|
|
45
|
+
`https://api.github.com/repos/${repo}/actions/workflows/${workflow}/runs` +
|
|
46
|
+
`?status=completed&per_page=1${branch ? `&branch=${branch}` : ''}`;
|
|
47
|
+
const res = await fetch(url, {
|
|
48
|
+
headers: {
|
|
49
|
+
Authorization: `Bearer ${token}`,
|
|
50
|
+
Accept: 'application/vnd.github+json',
|
|
51
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
if (res.status === 404) return null; // workflow not present (yet)
|
|
55
|
+
if (!res.ok) throw new Error(`GitHub API ${res.status} for ${workflow}: ${await res.text()}`);
|
|
56
|
+
const body = (await res.json()) as { workflow_runs?: RunInfo[] };
|
|
57
|
+
return body.workflow_runs?.[0] ?? null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function main(): Promise<number> {
|
|
61
|
+
const args = argv.slice(2);
|
|
62
|
+
const getOpt = (flag: string): string | undefined => {
|
|
63
|
+
const i = args.indexOf(flag);
|
|
64
|
+
return i >= 0 ? args[i + 1] : undefined;
|
|
65
|
+
};
|
|
66
|
+
const token = env.GITHUB_TOKEN ?? env.GH_TOKEN ?? '';
|
|
67
|
+
const repo = getOpt('--repo') ?? env.GITHUB_REPOSITORY ?? '';
|
|
68
|
+
const maxRedHours = Number(getOpt('--max-red-hours') ?? '48');
|
|
69
|
+
if (!token || !repo) {
|
|
70
|
+
process.stderr.write('❌ release-adjacent-health: GITHUB_TOKEN and GITHUB_REPOSITORY (or --repo) required.\n');
|
|
71
|
+
return 2;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Tag/release-triggered workflows have no branch association — query
|
|
75
|
+
// without the branch filter for those; branch-scoped for PR/push ones.
|
|
76
|
+
const branchless = new Set(['publish-npm.yml', 'deploy-mcp-worker.yml', 'release.yml']);
|
|
77
|
+
let red = 0;
|
|
78
|
+
for (const wf of WATCHED) {
|
|
79
|
+
let run: RunInfo | null;
|
|
80
|
+
try {
|
|
81
|
+
run = await latestCompletedRun(repo, token, wf, branchless.has(wf) ? '' : 'main');
|
|
82
|
+
} catch (err) {
|
|
83
|
+
process.stderr.write(`⚠️ ${wf}: ${(err as Error).message}\n`);
|
|
84
|
+
continue; // transient API failure must not fake a red
|
|
85
|
+
}
|
|
86
|
+
if (!run) {
|
|
87
|
+
process.stderr.write(`· ${wf}: no completed runs (or workflow absent) — skipping\n`);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (run.conclusion === 'success') {
|
|
91
|
+
process.stderr.write(`✅ ${wf}: last completed run green\n`);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const ageHours = (Date.now() - Date.parse(run.updated_at)) / 3_600_000;
|
|
95
|
+
if (ageHours >= maxRedHours) {
|
|
96
|
+
process.stdout.write(
|
|
97
|
+
`::error::release-adjacent workflow durably red: ${wf} — last completed run ` +
|
|
98
|
+
`'${run.conclusion}' ${ageHours.toFixed(0)}h ago with no newer success. ${run.html_url}\n`,
|
|
99
|
+
);
|
|
100
|
+
red += 1;
|
|
101
|
+
} else {
|
|
102
|
+
process.stderr.write(
|
|
103
|
+
`⚠️ ${wf}: last run '${run.conclusion}' ${ageHours.toFixed(0)}h ago (< ${maxRedHours}h window)\n`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (red > 0) {
|
|
108
|
+
process.stderr.write(`❌ ${red} release-adjacent workflow(s) durably red.\n`);
|
|
109
|
+
return 1;
|
|
110
|
+
}
|
|
111
|
+
process.stderr.write('✅ release-adjacent workflows healthy.\n');
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
main().then(
|
|
116
|
+
(code) => exit(code),
|
|
117
|
+
(err) => {
|
|
118
|
+
process.stderr.write(`❌ release-adjacent-health crashed: ${(err as Error).stack ?? err}\n`);
|
|
119
|
+
exit(2);
|
|
120
|
+
},
|
|
121
|
+
);
|