@event4u/agent-config 13.0.0 → 14.0.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 +160 -333
- package/README.md +1 -1
- package/dist/agent-src/commands/evals/roadmap.json +8 -0
- package/dist/agent-src/commands/fix/ci.md +39 -1
- package/dist/agent-src/commands/pr/create.md +65 -4
- package/dist/agent-src/commands/roadmap/next.md +28 -5
- package/dist/agent-src/commands/work.md +16 -0
- package/dist/agent-src/contexts/contracts/command-suggestion-flow.md +47 -0
- package/dist/agent-src/contexts/execution/contract-decision-sheet.md +94 -0
- package/dist/agent-src/contexts/execution/plan-confidence-gate.md +30 -0
- package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +21 -8
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +55 -8
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +42 -0
- package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +29 -0
- package/dist/agent-src/rules/command-suggestion-policy.md +33 -0
- package/dist/agent-src/rules/design-review-after-ui-write.md +25 -8
- package/dist/agent-src/rules/ui-audit-gate.md +25 -8
- package/dist/agent-src/scripts/gate_execute.ts +395 -0
- package/dist/agent-src/scripts/resume_probe.ts +422 -0
- package/dist/agent-src/scripts/roadmap_gates.ts +138 -4
- package/dist/agent-src/scripts/update_roadmap_progress.ts +53 -2
- package/dist/agent-src/skills/ai-council/SKILL.md +61 -0
- package/dist/agent-src/skills/experiment-loop/SKILL.md +116 -0
- package/dist/agent-src/skills/experiment-loop/evals/output-schema.yml +27 -0
- package/dist/agent-src/skills/experiment-loop/evals/triggers.json +17 -0
- package/dist/agent-src/skills/experiment-loop/references/pivot-ladder.md +54 -0
- package/dist/agent-src/skills/experiment-loop/references/protocol.md +61 -0
- package/dist/agent-src/skills/experiment-loop/references/register-format.md +66 -0
- package/dist/agent-src/skills/roadmap-management/SKILL.md +2 -2
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/subagent-orchestration/schemas/team-review-status.json +8 -3
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +43 -3
- package/dist/cli/registry.js +1 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-N7FEST6D.js → chunk-7PZ4FSZB.js} +351 -174
- package/dist/cli-delegate/{chunk-3B72LSZL.js → chunk-F3YUF7SA.js} +1 -1
- package/dist/cli-delegate/cmd_conformance.js +224 -104
- package/dist/cli-delegate/cmd_doctor.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +40 -18
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +6 -5
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +5 -4
- package/dist/hooks/dispatch.js +2018 -1320
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1652 -892
- package/dist/router.json +1 -1
- package/dist/scripts/condense.js +1 -1
- package/dist/scripts/condense.js.map +1 -1
- package/dist/ui/assets/{index-B0aMJnuz.js → index-C1pBRm1f.js} +2 -2
- package/dist/ui/assets/index-C1pBRm1f.js.map +1 -0
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
- package/src/agent-src/scripts/gate_execute.ts +395 -0
- package/src/agent-src/scripts/resume_probe.ts +422 -0
- package/src/agent-src/scripts/roadmap_gates.ts +138 -4
- package/src/agent-src/scripts/update_roadmap_progress.ts +53 -2
- package/src/config/ci-local-parity.yml +13 -0
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +111 -5
- package/src/config/gate-violation-baselines.json +2 -2
- package/src/config/rule-activation-census.json +41 -0
- package/src/config/rule-stub-ceilings.json +52 -0
- package/src/scripts/_dispatch.bash +4 -0
- package/src/scripts/_lib/evaluator_contract.ts +159 -0
- package/src/scripts/_lib/gh_transient.ts +80 -0
- package/src/scripts/_lib/release_material.ts +16 -5
- package/src/scripts/_lib/review_independence.ts +111 -0
- package/src/scripts/_lib/session_register.ts +14 -0
- package/src/scripts/_lib/skill_catalogue.ts +20 -0
- package/src/scripts/_lib/skill_catalogue_series.ts +411 -0
- package/src/scripts/_lib/turn_end_refusals.ts +482 -0
- package/src/scripts/ai_council/_py_json.ts +96 -0
- package/src/scripts/ai_council/cli_call_budget.ts +357 -0
- package/src/scripts/ai_council/clients.ts +80 -143
- package/src/scripts/ai_council/config.ts +25 -11
- package/src/scripts/ai_council/probe_store.ts +196 -0
- package/src/scripts/ai_council/qualification.ts +425 -0
- package/src/scripts/ai_council/qualification_wiring.ts +243 -0
- package/src/scripts/ai_council/quorum_wiring.ts +211 -0
- package/src/scripts/ai_team/team_dispatch.ts +40 -6
- package/src/scripts/capture_skill_catalogue.ts +264 -4
- package/src/scripts/check_evaluator_schema.ts +249 -0
- package/src/scripts/check_pr_ci_current.ts +409 -0
- package/src/scripts/check_review_schema.ts +195 -0
- package/src/scripts/check_rule_activation_census.ts +451 -0
- package/src/scripts/check_rule_stub_ceiling.ts +1128 -0
- package/src/scripts/condense.ts +1 -1
- package/src/scripts/council_cli.ts +141 -191
- package/src/scripts/dispatch_r2_reviewer.ts +8 -1
- package/src/scripts/hook_manifest.yaml +72 -5
- package/src/scripts/hooks/concern_registry.ts +4 -0
- package/src/scripts/hooks/dispatch_hook.ts +43 -1
- package/src/scripts/hooks/interruption_ledger_hook.ts +256 -0
- package/src/scripts/hooks/ship_diff_volume_hook.ts +180 -0
- package/src/scripts/hooks/turn_end_gate_hook.ts +119 -26
- package/src/scripts/hooks_doctor.ts +112 -0
- package/src/scripts/interruption_report.ts +443 -0
- package/src/scripts/lint_evidence_artifacts.ts +359 -0
- package/src/scripts/lint_roadmap_blockers.ts +65 -0
- package/src/scripts/lint_roadmap_complexity.ts +7 -1
- package/src/scripts/release.ts +38 -53
- package/src/scripts/rule_activation_census.ts +292 -0
- package/src/scripts/schemas/evaluator-output.schema.json +60 -0
- package/src/scripts/schemas/review-findings.schema.json +31 -0
- package/src/scripts/self_review_gate.ts +6 -0
- package/src/scripts/session_register_hook.ts +30 -1
- package/src/scripts/sync_pr_branch.ts +271 -0
- package/src/scripts/test_council_qualification.ts +213 -0
- package/dist/ui/assets/index-B0aMJnuz.js.map +0 -1
|
@@ -47,9 +47,11 @@
|
|
|
47
47
|
* non-sidechain user entries — is distinct for every real turn whatever
|
|
48
48
|
* the user typed, and stable across harness injections.
|
|
49
49
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
50
|
+
* No longer true, and corrected here rather than left as a stale admission:
|
|
51
|
+
* the files ARE pruned. `road-to-stop-gate-honesty` step 1.2 added a
|
|
52
|
+
* 90-day retention (`pruneAgedRefusalState`), run at `session_start` by the
|
|
53
|
+
* session-register concern. The record also COUNTS now instead of
|
|
54
|
+
* overwriting itself — see `markRefusedTurn`.
|
|
53
55
|
*
|
|
54
56
|
* The failure this ordering prevents is not a loop but a wedge: a turn that
|
|
55
57
|
* can never end. Layer 2 also covers the host that does not send
|
|
@@ -111,7 +113,6 @@
|
|
|
111
113
|
* not an advisory, so there is no advisory path here to put on the wrong code.
|
|
112
114
|
* Probed, not assumed.
|
|
113
115
|
*/
|
|
114
|
-
import * as crypto from 'node:crypto';
|
|
115
116
|
import * as fs from 'node:fs';
|
|
116
117
|
import * as path from 'node:path';
|
|
117
118
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
@@ -130,6 +131,14 @@ import { unwrap, type JsonObject, type JsonValue } from './envelope.js';
|
|
|
130
131
|
import { readHookStdin } from './hook_stdin.js';
|
|
131
132
|
import { atomic_write_json, is_replay_mode } from './state_io.js';
|
|
132
133
|
import { openRecordStats } from './subagent_ledger_hook.js';
|
|
134
|
+
import {
|
|
135
|
+
deriveSessionKey,
|
|
136
|
+
foldRefusal,
|
|
137
|
+
parseRecord,
|
|
138
|
+
readInstallBoundary,
|
|
139
|
+
sessionRefusalFile,
|
|
140
|
+
type RefusalRecord,
|
|
141
|
+
} from '../_lib/turn_end_refusals.js';
|
|
133
142
|
|
|
134
143
|
/** Dispatcher-internal block code. Pinned to 1 by `concern_block_exit_parity`. */
|
|
135
144
|
const EXIT_BLOCK = 1;
|
|
@@ -294,8 +303,12 @@ const PROMISSORY = [
|
|
|
294
303
|
* A legitimate hand-back is the opposite speech act: it gives the decision
|
|
295
304
|
* to the user and ends the turn on purpose. `scope-control` requires
|
|
296
305
|
* exactly this shape, so refusing it would put two rules in direct conflict.
|
|
306
|
+
*
|
|
307
|
+
* EXPORTED because `interruption_ledger_hook` classifies the same shape as a
|
|
308
|
+
* synchronous contact. Narrowing this list therefore changes a measurement as
|
|
309
|
+
* well as a refusal — check that hook before you touch it.
|
|
297
310
|
*/
|
|
298
|
-
const HANDBACK = [
|
|
311
|
+
export const HANDBACK = [
|
|
299
312
|
/\bdas entscheidest (du|Du)\b/i,
|
|
300
313
|
/\bdeine Entscheidung\b/i,
|
|
301
314
|
/\bich fasse .{0,40}nicht ungefragt an\b/i,
|
|
@@ -527,9 +540,49 @@ const _EDIT_TOOLS = new Set(['Edit', 'Write', 'MultiEdit', 'NotebookEdit']);
|
|
|
527
540
|
* detection when a project verifies by some command not listed here, and that is
|
|
528
541
|
* the safe direction for a gate that can refuse a turn: a false negative costs
|
|
529
542
|
* one unguarded turn, a false positive teaches the user to switch the gate off.
|
|
543
|
+
*
|
|
544
|
+
* ## Audited 2026-08-17 — `road-to-stop-gate-honesty` step 2.2
|
|
545
|
+
*
|
|
546
|
+
* 52 commands this project's own surface actually produces (its `package.json`
|
|
547
|
+
* scripts, its `Taskfile` targets, `./scripts-run`, `./agent-config`, and the
|
|
548
|
+
* PHP / Python / Go / Rust toolchains) were run through
|
|
549
|
+
* `isVerificationCommand`. The table of every one, with its verdict, is
|
|
550
|
+
* `turn_end_verify_allowlist.test.ts`; that file is the fixture step 2.2
|
|
551
|
+
* requires, and every addition below has its own row in it.
|
|
552
|
+
*
|
|
553
|
+
* The draft that opened the roadmap expected to add `phpunit` and `pest`. Claim
|
|
554
|
+
* 4 had already overtaken that — both matched before this audit, as did
|
|
555
|
+
* `composer test` and `php artisan test` — so what the audit actually found was
|
|
556
|
+
* two different gaps:
|
|
557
|
+
*
|
|
558
|
+
* · `phpstan` — a static analyser of exactly the class already listed
|
|
559
|
+
* (`mypy`, `pyright`, `clippy`). Added.
|
|
560
|
+
* · `lint` followed by a WORD character — `lint_persistence`,
|
|
561
|
+
* `lint_provenance`, and every other `lint_*` script in `src/scripts/`
|
|
562
|
+
* missed, because `\blint\b` needs a boundary and `_` is a word character.
|
|
563
|
+
* `lint[-_:a-z]*` is the exact mirror of the `check[-_:a-z]*` this list
|
|
564
|
+
* already carried, which is why it is the narrow fix rather than a new idea.
|
|
565
|
+
*
|
|
566
|
+
* Four further misses were found and deliberately NOT added, because Risk 2 of
|
|
567
|
+
* that roadmap is that every addition is a way to satisfy the gate without
|
|
568
|
+
* verifying anything:
|
|
569
|
+
*
|
|
570
|
+
* · `npm run prepack` — a lifecycle hook whose content is per-project. Here it
|
|
571
|
+
* validates; elsewhere it copies files. Recognising it would clear an
|
|
572
|
+
* unverified edit in any repo whose prepack is a build step.
|
|
573
|
+
* · `task sync` / `task generate-tools` / `agent-config roadmap:progress` —
|
|
574
|
+
* GENERATORS. They rewrite the tree; they check nothing. A generator that
|
|
575
|
+
* cleared the gate would be the rubber stamp in its purest form.
|
|
576
|
+
* · `agent-config gates --all` — enumerates gates, runs none.
|
|
577
|
+
* · `vendor/bin/rector process --dry-run` — a refactoring tool. Its dry run
|
|
578
|
+
* prints a diff; it does not assert anything holds.
|
|
579
|
+
*
|
|
580
|
+
* `psalm` is the obvious sibling of `phpstan` and is also NOT added: nothing in
|
|
581
|
+
* the audited surface runs it, and "the team actually runs" is the step's own
|
|
582
|
+
* standard. It is named here so the next audit does not re-derive the question.
|
|
530
583
|
*/
|
|
531
584
|
const _VERIFY_RE =
|
|
532
|
-
/\b(test|tests|vitest|jest|pytest|phpunit|pest|tsc|eslint|ruff|mypy|pyright|clippy|typecheck|lint
|
|
585
|
+
/\b(test|tests|vitest|jest|pytest|phpunit|pest|tsc|eslint|ruff|mypy|pyright|clippy|phpstan|typecheck|lint[-_:a-z]*|build|ci|preflight|smoke[-_:a-z]*|check[-_:a-z]*|validate[-_:a-z]*)\b|\b(task|npm|pnpm|yarn|composer|cargo|go|make|php artisan|bun)\s+(run\s+)?\S*(test|check|lint|build|ci|typecheck)/i;
|
|
533
586
|
|
|
534
587
|
export function isVerificationCommand(command: string): boolean {
|
|
535
588
|
return _VERIFY_RE.test(command);
|
|
@@ -579,29 +632,30 @@ export function detectUnverifiedEdit(toolCalls: readonly ToolCall[]): Finding |
|
|
|
579
632
|
// Re-entrancy — the guard, keyed on the turn, not on the reply
|
|
580
633
|
// ---------------------------------------------------------------------------
|
|
581
634
|
|
|
582
|
-
function stateDir(workspaceRoot: string): string {
|
|
583
|
-
return path.join(workspaceRoot, 'agents', 'runtime', 'state', 'turn-end-gate');
|
|
584
|
-
}
|
|
585
|
-
|
|
586
635
|
/**
|
|
587
636
|
* ONE file per session, not one per refused turn. R2 round 1, finding 17:
|
|
588
637
|
* per-turn files accumulated without bound and without a TTL, because "re-arms
|
|
589
638
|
* itself with no cleanup" described the key rotating, not the files being
|
|
590
639
|
* removed.
|
|
591
640
|
*
|
|
592
|
-
*
|
|
593
|
-
*
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
*
|
|
641
|
+
* Both halves are now closed. The per-turn MULTIPLICITY went first; the missing
|
|
642
|
+
* TTL was the other half and `road-to-stop-gate-honesty` step 1.2 shipped it as
|
|
643
|
+
* `pruneAgedRefusalState`, run at `session_start` by the session-register
|
|
644
|
+
* concern — the one slot that already prunes and therefore adds no spawn. The
|
|
645
|
+
* path and the retention constant live with the reader
|
|
646
|
+
* (`_lib/turn_end_refusals.ts`), because a pruner in one module and a writer in
|
|
647
|
+
* another must not each own their own idea of where the files are.
|
|
597
648
|
*/
|
|
598
649
|
function sessionStateFile(workspaceRoot: string, sessionKey: string): string {
|
|
599
|
-
return
|
|
650
|
+
return sessionRefusalFile(workspaceRoot, sessionKey);
|
|
600
651
|
}
|
|
601
652
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
653
|
+
/**
|
|
654
|
+
* Re-exported, not re-derived: the session register reads this session's own
|
|
655
|
+
* refusal record back for live per-session visibility, so the stem has two
|
|
656
|
+
* consumers and exactly one definition (`_lib/turn_end_refusals.ts`).
|
|
657
|
+
*/
|
|
658
|
+
export { deriveSessionKey };
|
|
605
659
|
|
|
606
660
|
/**
|
|
607
661
|
* A turn's identity is its ORDINAL — how many genuine user prompts the
|
|
@@ -641,19 +695,53 @@ export function alreadyRefusedTurn(
|
|
|
641
695
|
return false;
|
|
642
696
|
}
|
|
643
697
|
|
|
698
|
+
/**
|
|
699
|
+
* Write the re-entrancy marker AND count the refusal.
|
|
700
|
+
*
|
|
701
|
+
* The marker half is unchanged: `refused_turn` is what `alreadyRefusedTurn`
|
|
702
|
+
* reads, and nothing about the counting can alter whether a turn is refused
|
|
703
|
+
* twice. The counting half is `road-to-stop-gate-honesty` step 1.1 — the old
|
|
704
|
+
* record overwrote itself, so a session refused nine times looked exactly like a
|
|
705
|
+
* session refused once, and D-2's "refusal frequency is invisible" was true of
|
|
706
|
+
* the state file as much as of the absent reader.
|
|
707
|
+
*
|
|
708
|
+
* EVERY finding's detector is counted, not just the first. The first still lands
|
|
709
|
+
* in `detector` for compatibility with the 36 field records written before this,
|
|
710
|
+
* but a turn that trips B and C at once is two observations, and pooling them
|
|
711
|
+
* into one is what step 1.1 forbids in the reader — doing it in the writer would
|
|
712
|
+
* put the same defect somewhere the reader cannot fix.
|
|
713
|
+
*/
|
|
644
714
|
function markRefusedTurn(
|
|
645
715
|
workspaceRoot: string,
|
|
646
716
|
sessionKey: string,
|
|
647
717
|
turnOrdinal: number,
|
|
648
|
-
|
|
718
|
+
detectors: readonly DetectorId[],
|
|
649
719
|
): void {
|
|
650
720
|
if (is_replay_mode()) return;
|
|
721
|
+
const file = sessionStateFile(workspaceRoot, sessionKey);
|
|
722
|
+
let prev: RefusalRecord | null = null;
|
|
723
|
+
try {
|
|
724
|
+
prev = parseRecord(fs.readFileSync(file, 'utf-8'));
|
|
725
|
+
} catch {
|
|
726
|
+
// Absent or unreadable prior state starts a fresh count. Never a reason
|
|
727
|
+
// to skip the marker — the marker is the wedge guard.
|
|
728
|
+
}
|
|
729
|
+
let version: string | undefined;
|
|
730
|
+
try {
|
|
731
|
+
version = readInstallBoundary().version ?? undefined;
|
|
732
|
+
} catch {
|
|
733
|
+
version = undefined;
|
|
734
|
+
}
|
|
651
735
|
try {
|
|
652
|
-
atomic_write_json(
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
736
|
+
atomic_write_json(
|
|
737
|
+
file,
|
|
738
|
+
foldRefusal(prev, {
|
|
739
|
+
detectors,
|
|
740
|
+
turnOrdinal,
|
|
741
|
+
at: new Date().toISOString(),
|
|
742
|
+
version,
|
|
743
|
+
}) as unknown as Record<string, unknown>,
|
|
744
|
+
);
|
|
657
745
|
} catch {
|
|
658
746
|
// A state-write failure must never wedge the turn. Losing the marker
|
|
659
747
|
// costs at most one extra refusal; failing closed here would cost the
|
|
@@ -934,7 +1022,12 @@ export function main(): number {
|
|
|
934
1022
|
}
|
|
935
1023
|
if (findings.length === 0) return EXIT_ALLOW;
|
|
936
1024
|
|
|
937
|
-
markRefusedTurn(
|
|
1025
|
+
markRefusedTurn(
|
|
1026
|
+
workspaceRoot,
|
|
1027
|
+
sessionKey,
|
|
1028
|
+
turnOrdinal,
|
|
1029
|
+
findings.map((f) => f.detector),
|
|
1030
|
+
);
|
|
938
1031
|
|
|
939
1032
|
const lines = findings.map(
|
|
940
1033
|
(f) => ` · ${f.detector}: ${f.reason}\n evidence: ${JSON.stringify(f.evidence)}`,
|
|
@@ -34,6 +34,13 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
34
34
|
import { read_dispatch_issues } from "./hooks/dispatch_issues.js";
|
|
35
35
|
import { type JsonObject, type JsonValue, _load_yaml } from "./hooks/dispatch_hook.js";
|
|
36
36
|
import * as hooks_status from "./hooks_status.js";
|
|
37
|
+
import {
|
|
38
|
+
DETECTOR_IDS,
|
|
39
|
+
collectRefusalStats,
|
|
40
|
+
readInstallBoundary,
|
|
41
|
+
type InstallBoundary,
|
|
42
|
+
type RefusalStats,
|
|
43
|
+
} from "./_lib/turn_end_refusals.js";
|
|
37
44
|
|
|
38
45
|
// src/scripts/hooks_doctor.ts → parents[2] (../../..) is the repo root,
|
|
39
46
|
// mirroring the Python `Path(__file__).resolve().parents[2]`.
|
|
@@ -171,6 +178,17 @@ export interface DoctorPayload {
|
|
|
171
178
|
concerns: ConcernRow[];
|
|
172
179
|
trampolines: TrampolineRow[];
|
|
173
180
|
dispatch_issues: JsonObject[];
|
|
181
|
+
/**
|
|
182
|
+
* Turn-end refusals, per detector, from this workspace's own records.
|
|
183
|
+
*
|
|
184
|
+
* `road-to-stop-gate-honesty` § D-2: every advisory concern in this estate
|
|
185
|
+
* carries a registered kill standard and the one BLOCKING concern carries
|
|
186
|
+
* none, because nobody could see how often it fires. The doctor is where a
|
|
187
|
+
* concern's health is already read, so this is where the number belongs.
|
|
188
|
+
*/
|
|
189
|
+
turn_end_refusals: RefusalStats;
|
|
190
|
+
/** The machine's install stamp, for reading the version split below it. */
|
|
191
|
+
install_boundary: InstallBoundary;
|
|
174
192
|
}
|
|
175
193
|
|
|
176
194
|
/** Build the doctor payload — JSON-serialisable. */
|
|
@@ -229,12 +247,31 @@ export function collect(
|
|
|
229
247
|
issues = [];
|
|
230
248
|
}
|
|
231
249
|
|
|
250
|
+
let turn_end_refusals: RefusalStats;
|
|
251
|
+
try {
|
|
252
|
+
turn_end_refusals = collectRefusalStats(project_root);
|
|
253
|
+
} catch {
|
|
254
|
+
turn_end_refusals = {
|
|
255
|
+
sessionsWithRefusals: 0,
|
|
256
|
+
total: 0,
|
|
257
|
+
byDetector: { promissory: 0, language: 0, verification: 0, completion: 0 },
|
|
258
|
+
byPeriod: [],
|
|
259
|
+
legacyRecords: 0,
|
|
260
|
+
unversionedRecords: 0,
|
|
261
|
+
byVersion: [],
|
|
262
|
+
earliest: null,
|
|
263
|
+
latest: null,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
232
267
|
return {
|
|
233
268
|
schema_version: 1,
|
|
234
269
|
platforms: matrix.platforms,
|
|
235
270
|
concerns,
|
|
236
271
|
trampolines,
|
|
237
272
|
dispatch_issues: issues,
|
|
273
|
+
turn_end_refusals,
|
|
274
|
+
install_boundary: readInstallBoundary(),
|
|
238
275
|
};
|
|
239
276
|
}
|
|
240
277
|
|
|
@@ -242,6 +279,79 @@ function _strVal(v: JsonValue | undefined): string {
|
|
|
242
279
|
return v === undefined || v === null ? "undefined" : String(v);
|
|
243
280
|
}
|
|
244
281
|
|
|
282
|
+
/**
|
|
283
|
+
* The turn-end-gate's own number.
|
|
284
|
+
*
|
|
285
|
+
* Reports what the records can support and refuses the number they cannot: the
|
|
286
|
+
* denominator is *sessions that had at least one refusal*, never all sessions,
|
|
287
|
+
* because a session that was never refused writes no record. Labelling a
|
|
288
|
+
* per-refused-session figure as a per-session rate would be the same
|
|
289
|
+
* denominator inflation this estate's measurement discipline exists to stop.
|
|
290
|
+
*/
|
|
291
|
+
export function _render_refusals(payload: DoctorPayload): string[] {
|
|
292
|
+
const s = payload.turn_end_refusals;
|
|
293
|
+
const lines: string[] = [];
|
|
294
|
+
lines.push("Turn-end refusals");
|
|
295
|
+
lines.push("-".repeat(60));
|
|
296
|
+
if (s.sessionsWithRefusals === 0) {
|
|
297
|
+
lines.push("· no refusal records under agents/runtime/state/turn-end-gate/");
|
|
298
|
+
return lines;
|
|
299
|
+
}
|
|
300
|
+
const perSession = (s.total / s.sessionsWithRefusals).toFixed(2);
|
|
301
|
+
lines.push(
|
|
302
|
+
` ${s.total} refusal(s) across ${s.sessionsWithRefusals} session(s) ` +
|
|
303
|
+
`that were refused at least once — ${perSession} per such session.`,
|
|
304
|
+
);
|
|
305
|
+
lines.push(
|
|
306
|
+
" NOT a per-session rate: sessions with no refusal write no record, so " +
|
|
307
|
+
"the wider denominator is unavailable here by construction.",
|
|
308
|
+
);
|
|
309
|
+
if (s.earliest && s.latest) {
|
|
310
|
+
lines.push(` window: ${s.earliest.slice(0, 10)} … ${s.latest.slice(0, 10)}`);
|
|
311
|
+
}
|
|
312
|
+
for (const id of DETECTOR_IDS) {
|
|
313
|
+
const n = s.byDetector[id];
|
|
314
|
+
const share = s.total > 0 ? Math.round((n / s.total) * 100) : 0;
|
|
315
|
+
lines.push(` ${id.padEnd(14)} ${String(n).padStart(4)} ${share}%`);
|
|
316
|
+
}
|
|
317
|
+
if (s.legacyRecords > 0) {
|
|
318
|
+
lines.push(
|
|
319
|
+
` ${s.legacyRecords} record(s) predate per-detector counting and ` +
|
|
320
|
+
"contribute one refusal each — the total is a FLOOR, not an exact count.",
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
const boundary = payload.install_boundary;
|
|
324
|
+
if (boundary.version || boundary.installed_at) {
|
|
325
|
+
lines.push(
|
|
326
|
+
` installed: ${boundary.version ?? "(unknown version)"} at ` +
|
|
327
|
+
`${boundary.installed_at ?? "(unknown date)"}`,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
if (s.byVersion.length > 0) {
|
|
331
|
+
lines.push(" by recorded version:");
|
|
332
|
+
for (const v of s.byVersion) {
|
|
333
|
+
lines.push(` ${v.version.padEnd(16)} ${String(v.total).padStart(4)}`);
|
|
334
|
+
}
|
|
335
|
+
if (s.unversionedRecords > 0) {
|
|
336
|
+
lines.push(
|
|
337
|
+
" (unrecorded) rows predate version stamping — they cannot test " +
|
|
338
|
+
"the install-date prediction in either direction.",
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const recent = s.byPeriod.slice(0, 7);
|
|
343
|
+
if (recent.length > 0) {
|
|
344
|
+
lines.push(" most recent days:");
|
|
345
|
+
for (const p of recent) {
|
|
346
|
+
lines.push(
|
|
347
|
+
` ${p.period} ${String(p.total).padStart(3)} refusal(s) / ` +
|
|
348
|
+
`${p.sessions} session(s)`,
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return lines;
|
|
353
|
+
}
|
|
354
|
+
|
|
245
355
|
export function _render_table(payload: DoctorPayload): string {
|
|
246
356
|
const lines: string[] = [];
|
|
247
357
|
// Phase 1 CTA — surfaces at the TOP when issues exist, so a user
|
|
@@ -286,6 +396,8 @@ export function _render_table(payload: DoctorPayload): string {
|
|
|
286
396
|
const suffix = t.required ? "" : " (not required)";
|
|
287
397
|
lines.push(`${marker}${t.platform.padEnd(9)} ${t.expected}${suffix}`);
|
|
288
398
|
}
|
|
399
|
+
lines.push("");
|
|
400
|
+
lines.push(..._render_refusals(payload));
|
|
289
401
|
// Dispatch-issues detail — last 20 grouped by concern.
|
|
290
402
|
if (payload.dispatch_issues.length > 0) {
|
|
291
403
|
lines.push("");
|