@event4u/agent-config 8.8.0 → 8.10.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 +139 -263
- package/README.md +5 -5
- package/dist/agent-src/commands/agent-handoff.md +39 -2
- package/dist/agent-src/commands/cost/report.md +13 -3
- package/dist/agent-src/commands/research/deep.md +8 -0
- package/dist/agent-src/commands/research/report.md +7 -0
- package/dist/agent-src/commands/roadmap/create.md +12 -0
- package/dist/agent-src/commands/roadmap/process-full.md +31 -5
- package/dist/agent-src/commands/tests/create.md +28 -6
- package/dist/agent-src/commands/work.md +33 -0
- package/dist/agent-src/commands/worktree/cleanup.md +78 -0
- package/dist/agent-src/commands/worktree/create.md +62 -0
- package/dist/agent-src/commands/worktree/status.md +63 -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/communication/rules-auto/think-before-action-mechanics.md +30 -1
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +60 -31
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +37 -0
- package/dist/agent-src/contexts/execution/subagent-topologies.md +34 -0
- package/dist/agent-src/contexts/execution/verification-mechanics.md +9 -9
- package/dist/agent-src/rules/agent-authority.md +1 -1
- package/dist/agent-src/rules/communication-through-line.md +1 -1
- package/dist/agent-src/rules/delegation-policy.md +5 -0
- package/dist/agent-src/rules/design-fidelity.md +40 -0
- package/dist/agent-src/rules/direct-answers.md +1 -1
- package/dist/agent-src/rules/downstream-changes.md +1 -0
- package/dist/agent-src/rules/external-code-graph-interop.md +65 -0
- package/dist/agent-src/rules/git-history-discipline.md +18 -0
- package/dist/agent-src/rules/lethal-trifecta-guard.md +6 -1
- package/dist/agent-src/rules/minimal-safe-diff.md +58 -10
- package/dist/agent-src/rules/non-destructive-by-default.md +3 -0
- package/dist/agent-src/rules/question-not-instruction.md +76 -0
- package/dist/agent-src/rules/senior-engineering-discipline.md +1 -1
- package/dist/agent-src/rules/spreadsheet-source-quality.md +61 -0
- package/dist/agent-src/rules/think-before-action.md +1 -0
- package/dist/agent-src/rules/tool-safety.md +8 -0
- package/dist/agent-src/rules/untrusted-input-defense.md +87 -22
- package/dist/agent-src/scripts/update_roadmap_progress.ts +2 -1
- package/dist/agent-src/skills/accessibility-auditor/SKILL.md +19 -0
- package/dist/agent-src/skills/accessibility-auditor/evals/evals.json +15 -0
- package/dist/agent-src/skills/ai-code-blindspots/SKILL.md +10 -3
- package/dist/agent-src/skills/analysis-skill-router/evals/evals.json +13 -0
- package/dist/agent-src/skills/api-testing/SKILL.md +15 -1
- package/dist/agent-src/skills/character-consistency/evals/evals.json +16 -0
- package/dist/agent-src/skills/code-review/SKILL.md +2 -0
- package/dist/agent-src/skills/command-routing/evals/evals.json +14 -0
- package/dist/agent-src/skills/command-writing/SKILL.md +53 -40
- package/dist/agent-src/skills/dashboard-design/evals/evals.json +16 -0
- package/dist/agent-src/skills/dcf-modeling/evals/domain-truth.json +29 -0
- 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/deep-reading-analyst/SKILL.md +1 -0
- package/dist/agent-src/skills/design-intelligence/SKILL.md +49 -12
- package/dist/agent-src/skills/design-intelligence/evals/evals.json +14 -0
- package/dist/agent-src/skills/design-review/SKILL.md +35 -15
- package/dist/agent-src/skills/design-system-capture/SKILL.md +84 -24
- 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 +27 -0
- package/dist/agent-src/skills/doc-coauthoring/evals/evals.json +16 -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 +36 -5
- package/dist/agent-src/skills/fe-design/evals/triggers.json +47 -0
- package/dist/agent-src/skills/forecasting/evals/domain-truth.json +29 -0
- package/dist/agent-src/skills/forecasting/evals/evals.json +20 -0
- package/dist/agent-src/skills/frontend-render-security/SKILL.md +2 -2
- 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/html-deck/SKILL.md +27 -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/launch-readiness/evals/evals.json +21 -0
- package/dist/agent-src/skills/logging-monitoring/evals/evals.json +16 -0
- package/dist/agent-src/skills/markitdown/SKILL.md +1 -0
- package/dist/agent-src/skills/mcp/SKILL.md +24 -0
- package/dist/agent-src/skills/mcp-builder/SKILL.md +9 -1
- package/dist/agent-src/skills/memory-consolidation/SKILL.md +55 -4
- 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/perf-feedback-craft/evals/evals.json +16 -0
- package/dist/agent-src/skills/pest-testing/SKILL.md +1 -1
- 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/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/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 +39 -5
- package/dist/agent-src/skills/runway-cognition/evals/domain-truth.json +29 -0
- package/dist/agent-src/skills/runway-cognition/evals/evals.json +20 -0
- package/dist/agent-src/skills/scenario-modeling/evals/domain-truth.json +29 -0
- package/dist/agent-src/skills/scenario-modeling/evals/evals.json +20 -0
- package/dist/agent-src/skills/secrets-management/SKILL.md +7 -0
- package/dist/agent-src/skills/skill-writing/SKILL.md +12 -3
- 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 +27 -33
- package/dist/agent-src/skills/supply-chain-intake/SKILL.md +12 -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 +34 -4
- 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/ui-component-architect/SKILL.md +9 -0
- package/dist/agent-src/skills/unit-economics-modeling/evals/domain-truth.json +40 -0
- package/dist/agent-src/skills/unit-economics-modeling/evals/evals.json +24 -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 +180 -0
- package/dist/agent-src/skills/worktree-lifecycle/evals/triggers.json +52 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +36 -0
- package/dist/cli/registry.js +3 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +326 -67
- 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 +25 -14
- package/dist/discovery/trust-report.md +4 -4
- package/dist/discovery/workspaces.json +25 -12
- package/dist/install/install.mjs +136 -1
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/claude_builtin_names.js +145 -0
- package/dist/scripts/_lib/claude_builtin_names.js.map +1 -0
- package/dist/scripts/condense.js +14 -0
- package/dist/scripts/condense.js.map +1 -1
- package/docs/CLAIMS.md +36 -8
- package/docs/architecture.md +4 -4
- package/docs/archive/CHANGELOG-pre-8.9.0.md +306 -0
- package/docs/benchmark.md +113 -0
- package/docs/catalog.md +32 -8
- package/docs/command-flows.md +6 -1
- package/docs/contracts/CHANGELOG-conventions.md +6 -0
- package/docs/contracts/command-clusters.md +18 -0
- package/docs/contracts/design-artifact-lifecycle.md +93 -0
- package/docs/contracts/design-artifact-verification.md +153 -0
- package/docs/contracts/injected-block-authenticity-model.md +66 -0
- package/docs/contracts/mcp-tool-inventory.md +28 -28
- package/docs/contracts/release-pr-gating.md +26 -0
- package/docs/contracts/release-sizing.md +85 -0
- package/docs/contracts/surface-agent-contracts.md +153 -0
- package/docs/decisions/ADR-061-corpus-grounding-layer.md +13 -0
- package/docs/decisions/ADR-115-command-cluster-consolidation-phase-4.md +1 -1
- package/docs/decisions/ADR-116-memory-tripwire-activation-path.md +9 -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/asking-and-brevity-examples.md +27 -0
- package/docs/guidelines/agent-infra/emphasis-budget.md +45 -0
- package/docs/guidelines/agent-infra/simplicity-and-goal-demos.md +151 -0
- package/docs/guidelines/agent-infra/tool-description-as-policy.md +35 -0
- package/docs/guidelines/design-canon.md +1 -0
- package/docs/proof.md +38 -23
- package/docs/release-runbook.md +3 -0
- package/docs/second-brain-scope.md +69 -26
- package/docs/self-review-gate.md +51 -0
- package/package.json +1 -1
- package/src/agent-src/scripts/update_roadmap_progress.ts +2 -1
- package/src/scripts/_dispatch.bash +27 -0
- package/src/scripts/_lib/claude_builtin_names.ts +148 -0
- package/src/scripts/_lib/file_slicer.ts +117 -0
- package/src/scripts/_lib/lexical_index.ts +155 -0
- package/src/scripts/_lib/orchestration_record.ts +183 -0
- package/src/scripts/_lib/orchestration_savings.ts +114 -9
- package/src/scripts/_lib/retrieval_sanitize.ts +71 -0
- package/src/scripts/_lib/security_lint.ts +1 -0
- package/src/scripts/_lib/stat_index.ts +98 -0
- package/src/scripts/benchmark.ts +122 -0
- package/src/scripts/build_proof.ts +30 -17
- package/src/scripts/check_memory.ts +2 -0
- package/src/scripts/check_quality_regression.ts +51 -0
- package/src/scripts/check_release_adjacent_health.ts +121 -0
- package/src/scripts/check_roadmap_trackable.ts +3 -2
- package/src/scripts/check_rule_invariants.ts +275 -0
- package/src/scripts/condense.ts +14 -0
- package/src/scripts/consumer_matrix.ts +353 -0
- package/src/scripts/discovery_graph.ts +0 -0
- package/src/scripts/external_sources_denylist.json +4 -1
- package/src/scripts/generate_index.ts +6 -2
- package/src/scripts/install-hooks.sh +25 -0
- package/src/scripts/install.ts +35 -2
- package/src/scripts/learning_sidecar.ts +0 -0
- package/src/scripts/lint_agent_skill_names.ts +29 -0
- package/src/scripts/lint_changelog_rollback.ts +293 -0
- package/src/scripts/lint_knowledge_scale.ts +4 -3
- package/src/scripts/lint_roadmap_complexity.ts +124 -0
- package/src/scripts/lint_versioned_cache.ts +195 -0
- package/src/scripts/mcp_server/consumer_tool_catalog.json +2 -1
- package/src/scripts/mcp_server/tools.ts +24 -1
- package/src/scripts/measure_lexical_ranking.ts +173 -0
- package/src/scripts/memory_lookup.ts +0 -0
- package/src/scripts/orchestration_record.ts +156 -0
- package/src/scripts/orchestration_savings_report.ts +65 -8
- package/src/scripts/schemas/skill.schema.json +4 -0
- package/src/scripts/score_domain_truth.ts +192 -0
- package/src/scripts/second_brain_retrieval.ts +320 -0
- package/src/scripts/second_brain_run.ts +341 -0
- package/src/scripts/self_review_gate.test.ts +65 -0
- package/src/scripts/self_review_gate.ts +272 -0
- package/src/scripts/trigger_eval_grandfather.json +223 -224
|
@@ -5,16 +5,28 @@
|
|
|
5
5
|
* Consumes the `orchestration` sub-object of audit-log-v1 JSONL lines
|
|
6
6
|
* (contract: `src/agent-src/contexts/execution/orchestration-telemetry.md`).
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
8
|
+
* TWO metrics, kept distinct on purpose:
|
|
9
|
+
* 1. MEASURED token counts — `token_delta` (net token-COUNT delta vs the
|
|
10
|
+
* in-session baseline; negative = fewer tokens). Real, but blind to the
|
|
11
|
+
* downshift RATE win: the same work on a cheaper tier uses ~the same token
|
|
12
|
+
* COUNT, so token_delta ≈ 0 for a pure downshift even though cost dropped.
|
|
13
|
+
* 2. MODELED cost-% (`modeled_cost`) — captures exactly that downshift win via
|
|
14
|
+
* provider-neutral tier weights + absolute `dispatch_tokens`. It is a MODEL
|
|
15
|
+
* (weights + a same-token-count assumption), NOT a measured $ figure, and is
|
|
16
|
+
* clearly labelled as such in the report notes.
|
|
14
17
|
*/
|
|
15
18
|
|
|
16
19
|
export type TokenDeltaProvenance = 'measured' | 'estimated';
|
|
17
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Relative, provider-NEUTRAL cost weights per model tier — a tunable ratio, not
|
|
23
|
+
* $ prices. Default reflects the ~5×/~15× cheap-vs-frontier ratios from the
|
|
24
|
+
* cost-downshift council; override per project via the report's `--weights`.
|
|
25
|
+
* Used ONLY for the modeled cost-%, never for the measured token counts.
|
|
26
|
+
*/
|
|
27
|
+
export type TierWeights = Record<string, number>;
|
|
28
|
+
export const DEFAULT_TIER_WEIGHTS: TierWeights = { lite: 1, medium: 5, high: 15 };
|
|
29
|
+
|
|
18
30
|
/** The `orchestration` sub-object shape we read (subset — additive fields only). */
|
|
19
31
|
export interface OrchestrationRecord {
|
|
20
32
|
spawn_count?: number;
|
|
@@ -23,6 +35,14 @@ export interface OrchestrationRecord {
|
|
|
23
35
|
tier_chosen?: string | null;
|
|
24
36
|
task_class?: string | null;
|
|
25
37
|
outcome?: string;
|
|
38
|
+
/** Absolute measured tokens the dispatched slice consumed (for the modeled cost-%). */
|
|
39
|
+
dispatch_tokens?: number | null;
|
|
40
|
+
/** The orchestrator's own tier — the baseline the downshift is measured against. */
|
|
41
|
+
session_tier?: string | null;
|
|
42
|
+
/** QUALITY: subagent return adopted without parent rework. null/absent = pre-extension line / not measured. */
|
|
43
|
+
first_pass_success?: boolean | null;
|
|
44
|
+
/** QUALITY: slice retried on a higher tier after a verification failure. null/absent = pre-extension line / not measured. */
|
|
45
|
+
escalated?: boolean | null;
|
|
26
46
|
}
|
|
27
47
|
|
|
28
48
|
/** A parsed audit-log-v1 line (only the fields this aggregator touches). */
|
|
@@ -37,6 +57,33 @@ export interface ProvenanceStat {
|
|
|
37
57
|
net_token_delta: number;
|
|
38
58
|
}
|
|
39
59
|
|
|
60
|
+
export interface ModeledCost {
|
|
61
|
+
/** Dispatches with dispatch_tokens + session_tier + tier_chosen all weight-resolvable. */
|
|
62
|
+
covered_dispatches: number;
|
|
63
|
+
/** Σ dispatch_tokens × weight(session_tier) — same tokens costed on the baseline tier. */
|
|
64
|
+
baseline_cost_units: number;
|
|
65
|
+
/** Σ dispatch_tokens × weight(tier_chosen) — modeled cost of the downshifted run. */
|
|
66
|
+
delegated_cost_units: number;
|
|
67
|
+
/** (baseline − delegated) / baseline, 0..1; null when no covered dispatches. MODELED, not measured $. */
|
|
68
|
+
cost_reduction_pct: number | null;
|
|
69
|
+
weights: TierWeights;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface QualityStats {
|
|
73
|
+
/** Dispatches carrying at least one of the two quality booleans (the n behind the ≥20 pairing gate). */
|
|
74
|
+
quality_lines: number;
|
|
75
|
+
/** Dispatches carrying a boolean first_pass_success. */
|
|
76
|
+
first_pass_lines: number;
|
|
77
|
+
first_pass_success_count: number;
|
|
78
|
+
/** first_pass_success_count / first_pass_lines, 0..1; null when no line carries the field. */
|
|
79
|
+
first_pass_success_rate: number | null;
|
|
80
|
+
/** Dispatches carrying a boolean escalated. */
|
|
81
|
+
escalated_lines: number;
|
|
82
|
+
escalated_count: number;
|
|
83
|
+
/** escalated_count / escalated_lines, 0..1; null when no line carries the field. */
|
|
84
|
+
escalation_rate: number | null;
|
|
85
|
+
}
|
|
86
|
+
|
|
40
87
|
export interface SavingsReport {
|
|
41
88
|
/** Lines carrying an orchestration object with spawn_count > 0. */
|
|
42
89
|
dispatches: number;
|
|
@@ -54,6 +101,10 @@ export interface SavingsReport {
|
|
|
54
101
|
by_task_class: Record<string, number>;
|
|
55
102
|
/** measured dispatches / dispatches, 0..1 (0 when no dispatches). */
|
|
56
103
|
measured_share: number;
|
|
104
|
+
/** Modeled downshift cost-% (tier-weight based). Distinct from the measured token counts. */
|
|
105
|
+
modeled_cost: ModeledCost;
|
|
106
|
+
/** QUALITY dimension — paired with cost per council verdict; never report savings alone. */
|
|
107
|
+
quality: QualityStats;
|
|
57
108
|
/** Honest caveats about what the number does and does not mean. */
|
|
58
109
|
notes: string[];
|
|
59
110
|
}
|
|
@@ -70,7 +121,10 @@ function emptyProvenance(): ProvenanceStat {
|
|
|
70
121
|
* dispatch). Unknown/absent fields default safely (token_delta → 0, provenance
|
|
71
122
|
* → estimated) so a pre-extension line never throws.
|
|
72
123
|
*/
|
|
73
|
-
export function aggregateOrchestrationSavings(
|
|
124
|
+
export function aggregateOrchestrationSavings(
|
|
125
|
+
lines: AuditLine[],
|
|
126
|
+
weights: TierWeights = DEFAULT_TIER_WEIGHTS,
|
|
127
|
+
): SavingsReport {
|
|
74
128
|
const report: SavingsReport = {
|
|
75
129
|
dispatches: 0,
|
|
76
130
|
total_spawns: 0,
|
|
@@ -81,6 +135,16 @@ export function aggregateOrchestrationSavings(lines: AuditLine[]): SavingsReport
|
|
|
81
135
|
by_tier: {},
|
|
82
136
|
by_task_class: {},
|
|
83
137
|
measured_share: 0,
|
|
138
|
+
modeled_cost: { covered_dispatches: 0, baseline_cost_units: 0, delegated_cost_units: 0, cost_reduction_pct: null, weights },
|
|
139
|
+
quality: {
|
|
140
|
+
quality_lines: 0,
|
|
141
|
+
first_pass_lines: 0,
|
|
142
|
+
first_pass_success_count: 0,
|
|
143
|
+
first_pass_success_rate: null,
|
|
144
|
+
escalated_lines: 0,
|
|
145
|
+
escalated_count: 0,
|
|
146
|
+
escalation_rate: null,
|
|
147
|
+
},
|
|
84
148
|
notes: [],
|
|
85
149
|
};
|
|
86
150
|
|
|
@@ -109,18 +173,59 @@ export function aggregateOrchestrationSavings(lines: AuditLine[]): SavingsReport
|
|
|
109
173
|
|
|
110
174
|
const cls = o.task_class ?? 'unclassified';
|
|
111
175
|
report.by_task_class[cls] = (report.by_task_class[cls] ?? 0) + delta;
|
|
176
|
+
|
|
177
|
+
// Modeled cost — the downshift RATE win token_delta (counts) cannot see:
|
|
178
|
+
// the same dispatch_tokens on a cheaper tier cost less by the weight ratio.
|
|
179
|
+
const dt = o.dispatch_tokens ?? 0;
|
|
180
|
+
const wChosen = o.tier_chosen != null ? weights[o.tier_chosen] : undefined;
|
|
181
|
+
const wSession = o.session_tier != null ? weights[o.session_tier] : undefined;
|
|
182
|
+
if (dt > 0 && wChosen !== undefined && wSession !== undefined) {
|
|
183
|
+
report.modeled_cost.covered_dispatches += 1;
|
|
184
|
+
report.modeled_cost.baseline_cost_units += dt * wSession;
|
|
185
|
+
report.modeled_cost.delegated_cost_units += dt * wChosen;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Quality dimension — only booleans count; null/absent = pre-extension
|
|
189
|
+
// line / not measured (never a silent false).
|
|
190
|
+
const q = report.quality;
|
|
191
|
+
const fps = typeof o.first_pass_success === 'boolean';
|
|
192
|
+
const esc = typeof o.escalated === 'boolean';
|
|
193
|
+
if (fps || esc) q.quality_lines += 1;
|
|
194
|
+
if (fps) {
|
|
195
|
+
q.first_pass_lines += 1;
|
|
196
|
+
if (o.first_pass_success === true) q.first_pass_success_count += 1;
|
|
197
|
+
}
|
|
198
|
+
if (esc) {
|
|
199
|
+
q.escalated_lines += 1;
|
|
200
|
+
if (o.escalated === true) q.escalated_count += 1;
|
|
201
|
+
}
|
|
112
202
|
}
|
|
113
203
|
|
|
114
204
|
report.measured_share = report.dispatches === 0 ? 0 : report.by_provenance.measured.dispatches / report.dispatches;
|
|
115
205
|
|
|
206
|
+
const mc = report.modeled_cost;
|
|
207
|
+
mc.cost_reduction_pct = mc.baseline_cost_units > 0 ? (mc.baseline_cost_units - mc.delegated_cost_units) / mc.baseline_cost_units : null;
|
|
208
|
+
|
|
209
|
+
const q = report.quality;
|
|
210
|
+
q.first_pass_success_rate = q.first_pass_lines > 0 ? q.first_pass_success_count / q.first_pass_lines : null;
|
|
211
|
+
q.escalation_rate = q.escalated_lines > 0 ? q.escalated_count / q.escalated_lines : null;
|
|
212
|
+
|
|
116
213
|
// Honest caveats — always attached so a consumer never over-reads the number.
|
|
117
214
|
if (report.dispatches === 0) {
|
|
118
|
-
report.notes.push('No orchestration telemetry yet (0 dispatches).
|
|
215
|
+
report.notes.push('No orchestration telemetry yet (0 dispatches). Data accrues as `subagents.auto: on` delegates real slices (recorded via orchestration_record).');
|
|
119
216
|
} else {
|
|
120
|
-
report.notes.push('
|
|
217
|
+
report.notes.push('Token counts are MEASURED but blind to the downshift rate win (same tokens on a cheaper tier ≈ same count). The MODELED cost-% below captures that axis.');
|
|
121
218
|
if (report.by_provenance.estimated.dispatches > 0) {
|
|
122
219
|
report.notes.push(`${report.by_provenance.estimated.dispatches}/${report.dispatches} dispatch(es) use ESTIMATED token_delta (chars/4, lossy). Prefer measured (host usage metadata).`);
|
|
123
220
|
}
|
|
221
|
+
if (mc.covered_dispatches > 0 && mc.cost_reduction_pct !== null) {
|
|
222
|
+
report.notes.push(`MODELED cost reduction ${(mc.cost_reduction_pct * 100).toFixed(0)}% over ${mc.covered_dispatches}/${report.dispatches} dispatch(es) that carry tier data — from provider-neutral tier weights ${JSON.stringify(mc.weights)} + a same-token-count assumption. A MODEL, NOT a measured $ figure; tune with --weights.`);
|
|
223
|
+
} else {
|
|
224
|
+
report.notes.push('MODELED cost-% unavailable: no dispatch yet carries dispatch_tokens + session_tier + tier_chosen. Record those (orchestration_record --dispatch-tokens/--session-tier/--tier-chosen) to populate it.');
|
|
225
|
+
}
|
|
226
|
+
if (q.quality_lines === 0) {
|
|
227
|
+
report.notes.push('QUALITY columns empty: no dispatch yet carries first_pass_success / escalated. Record them (orchestration_record --first-pass-success/--escalated) — cost and quality are reported as a PAIR, never savings alone.');
|
|
228
|
+
}
|
|
124
229
|
}
|
|
125
230
|
|
|
126
231
|
return report;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitize floor for retrieval read-surfaces
|
|
3
|
+
* (road-to-retrieval-substrate-hardening B6).
|
|
4
|
+
*
|
|
5
|
+
* Corpus content — especially user-ingested knowledge chunks under
|
|
6
|
+
* `agents/memory/knowledge/` — flows into the agent context via `retrieve_v1`
|
|
7
|
+
* / `memory_get_v1`. An attacker (or a compromised ingested source) can embed
|
|
8
|
+
* hidden-instruction vectors (bidi controls, zero-width chars, Unicode Tag
|
|
9
|
+
* block) or control-char noise that the model may act on — the untrusted-input
|
|
10
|
+
* / lethal-trifecta injection surface. This floor strips those vectors from
|
|
11
|
+
* every string field before it is emitted, treating retrieved content as DATA.
|
|
12
|
+
*
|
|
13
|
+
* It deliberately does NOT rewrite visible text (that would corrupt legitimate
|
|
14
|
+
* rule/chunk bodies and code snippets) — the injection risk is the *invisible*
|
|
15
|
+
* layer + unbounded length; visible content is the agent's to read as data.
|
|
16
|
+
* Codepoint classes are shared with `lint_hidden_unicode` (one source of truth
|
|
17
|
+
* for what counts as a hidden-instruction vector).
|
|
18
|
+
*/
|
|
19
|
+
import { _classify } from '../lint_hidden_unicode.js';
|
|
20
|
+
|
|
21
|
+
/** Hard per-field length cap — bounds a runaway/adversarial body. */
|
|
22
|
+
export const MAX_FIELD_CHARS = 8192;
|
|
23
|
+
|
|
24
|
+
/** True for a C0/C1 control char that is NOT a benign whitespace (\t, \n). */
|
|
25
|
+
function _isStrippableControl(cp: number): boolean {
|
|
26
|
+
if (cp === 0x09 || cp === 0x0a) return false; // keep tab + newline
|
|
27
|
+
if (cp <= 0x1f) return true; // C0 controls (incl. CR 0x0d — normalised out)
|
|
28
|
+
if (cp === 0x7f) return true; // DEL
|
|
29
|
+
if (cp >= 0x80 && cp <= 0x9f) return true; // C1 controls
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Strip hidden-instruction vectors (bidi / zero-width / Unicode-tag /
|
|
35
|
+
* deprecated-format, per `_classify`) and stray control chars from a string,
|
|
36
|
+
* then cap its length. Preserves all visible content + tabs/newlines.
|
|
37
|
+
*/
|
|
38
|
+
export function sanitize_text(s: string): string {
|
|
39
|
+
let out = '';
|
|
40
|
+
for (const ch of s) {
|
|
41
|
+
const cp = ch.codePointAt(0);
|
|
42
|
+
if (cp === undefined) continue;
|
|
43
|
+
if (_classify(cp) !== null) continue; // hidden-instruction vector → drop
|
|
44
|
+
if (_isStrippableControl(cp)) continue; // control-char noise → drop
|
|
45
|
+
out += ch;
|
|
46
|
+
}
|
|
47
|
+
return out.length > MAX_FIELD_CHARS ? out.slice(0, MAX_FIELD_CHARS) : out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Return a shallow copy of a retrieval entry with every string value (and
|
|
52
|
+
* one level of string-array values) sanitized. Non-string values pass through
|
|
53
|
+
* unchanged (numbers, booleans, the PyFloat marker, nested plain objects are
|
|
54
|
+
* sanitized recursively).
|
|
55
|
+
*/
|
|
56
|
+
export function sanitize_entry(entry: Record<string, unknown>): Record<string, unknown> {
|
|
57
|
+
const out: Record<string, unknown> = {};
|
|
58
|
+
for (const [k, v] of Object.entries(entry)) {
|
|
59
|
+
out[k] = _sanitize_value(v);
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function _sanitize_value(v: unknown): unknown {
|
|
65
|
+
if (typeof v === 'string') return sanitize_text(v);
|
|
66
|
+
if (Array.isArray(v)) return v.map((x) => (typeof x === 'string' ? sanitize_text(x) : _sanitize_value(x)));
|
|
67
|
+
if (v && typeof v === 'object' && v.constructor === Object) {
|
|
68
|
+
return sanitize_entry(v as Record<string, unknown>);
|
|
69
|
+
}
|
|
70
|
+
return v; // numbers, booleans, null, marker classes (PyFloat/PyTimestamp) untouched
|
|
71
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stat-index — skip an expensive scan when the input tree is unchanged
|
|
3
|
+
* (road-to-retrieval-substrate-hardening B5a).
|
|
4
|
+
*
|
|
5
|
+
* The memory / knowledge / discovery scanners re-read their whole file set on
|
|
6
|
+
* every `stop` / `session_start` hook, even when nothing changed. A stat-index
|
|
7
|
+
* captures a cheap signature of the input files (size + mtime_ns) and caches
|
|
8
|
+
* the scan's result beside it; a later call with the same signature returns the
|
|
9
|
+
* cached payload without re-scanning. The cache is version-namespaced (so a
|
|
10
|
+
* tool bump invalidates cleanly, versioned-cache lint B5b) and written
|
|
11
|
+
* atomically (pid-temp + rename) so concurrent hook invocations stay
|
|
12
|
+
* idempotent (council Q3).
|
|
13
|
+
*
|
|
14
|
+
* `size + mtime_ns` is sufficient for gating rebuild decisions (the council
|
|
15
|
+
* withdrew the crypto-binding demand as a non-problem); a `--force` bypass and
|
|
16
|
+
* an explicit `clear()` cover the rare stale-mtime case (e.g. `cp -p`).
|
|
17
|
+
*/
|
|
18
|
+
import * as fs from 'node:fs';
|
|
19
|
+
import * as path from 'node:path';
|
|
20
|
+
import process from 'node:process';
|
|
21
|
+
|
|
22
|
+
/** A deterministic signature over a file set: `<count>:<Σsize>:<maxMtimeNs>:<hashOfPaths>`. */
|
|
23
|
+
export function statSignature(files: readonly string[]): string {
|
|
24
|
+
let count = 0;
|
|
25
|
+
let totalSize = 0;
|
|
26
|
+
let maxMtimeNs = 0n;
|
|
27
|
+
const sortedPaths = [...files].sort();
|
|
28
|
+
for (const f of sortedPaths) {
|
|
29
|
+
let st: fs.Stats;
|
|
30
|
+
try {
|
|
31
|
+
st = fs.statSync(f, { bigint: true }) as unknown as fs.Stats;
|
|
32
|
+
} catch {
|
|
33
|
+
continue; // missing file → excluded from the signature
|
|
34
|
+
}
|
|
35
|
+
count += 1;
|
|
36
|
+
totalSize += Number((st as unknown as { size: bigint }).size);
|
|
37
|
+
const m = (st as unknown as { mtimeNs: bigint }).mtimeNs;
|
|
38
|
+
if (m > maxMtimeNs) maxMtimeNs = m;
|
|
39
|
+
}
|
|
40
|
+
// A stable hash of the path list so add/remove/rename shifts the signature
|
|
41
|
+
// even when count + size + mtime coincide.
|
|
42
|
+
let h = 5381;
|
|
43
|
+
for (const p of sortedPaths) {
|
|
44
|
+
for (let i = 0; i < p.length; i++) h = ((h * 33) ^ p.charCodeAt(i)) >>> 0;
|
|
45
|
+
}
|
|
46
|
+
return `${count}:${totalSize}:${maxMtimeNs.toString()}:${h.toString(16)}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface CacheFile<T> {
|
|
50
|
+
schema_version: number;
|
|
51
|
+
signature: string;
|
|
52
|
+
payload: T;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Return the cached payload when the file-set signature is unchanged; otherwise
|
|
57
|
+
* run `compute`, persist the result under `signature`, and return it.
|
|
58
|
+
*
|
|
59
|
+
* @param cachePath version-namespaced cache file (e.g. `…-index-v1.json`)
|
|
60
|
+
* @param files the input file set whose stat signature gates the cache
|
|
61
|
+
* @param compute the expensive scan, run only on a miss
|
|
62
|
+
* @param force bypass the cache and always recompute (then persist)
|
|
63
|
+
*/
|
|
64
|
+
export function scanCached<T>(
|
|
65
|
+
cachePath: string,
|
|
66
|
+
files: readonly string[],
|
|
67
|
+
compute: () => T,
|
|
68
|
+
force = false,
|
|
69
|
+
): T {
|
|
70
|
+
const signature = statSignature(files);
|
|
71
|
+
if (!force) {
|
|
72
|
+
try {
|
|
73
|
+
const cached = JSON.parse(fs.readFileSync(cachePath, 'utf-8')) as CacheFile<T>;
|
|
74
|
+
if (cached.signature === signature) return cached.payload;
|
|
75
|
+
} catch {
|
|
76
|
+
/* miss → recompute */
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const payload = compute();
|
|
80
|
+
writeCacheAtomic<T>(cachePath, { schema_version: 1, signature, payload });
|
|
81
|
+
return payload;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function writeCacheAtomic<T>(cachePath: string, data: CacheFile<T>): void {
|
|
85
|
+
fs.mkdirSync(path.dirname(cachePath), { recursive: true });
|
|
86
|
+
const tmp = `${cachePath}.${process.pid}.tmp`;
|
|
87
|
+
fs.writeFileSync(tmp, JSON.stringify(data));
|
|
88
|
+
fs.renameSync(tmp, cachePath);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Drop a stat-index cache file (for `--force`-style resets / tests). */
|
|
92
|
+
export function clear(cachePath: string): void {
|
|
93
|
+
try {
|
|
94
|
+
fs.rmSync(cachePath);
|
|
95
|
+
} catch {
|
|
96
|
+
/* already absent */
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* `agent-config benchmark` — self-measuring token-reduction report
|
|
4
|
+
* (road-to-retrieval-substrate-hardening B7a).
|
|
5
|
+
*
|
|
6
|
+
* Reports the context-token reduction of the retrieval economy against the
|
|
7
|
+
* REAL alternative: the FULL always-loaded projection (council Q4) — the
|
|
8
|
+
* honest "what the user pays if everything loads eagerly + full descriptions".
|
|
9
|
+
* NOT a synthetic full-corpus strawman (explicitly rejected).
|
|
10
|
+
*
|
|
11
|
+
* Baseline components (from the pinned internal/bench/reports/token-baseline.json):
|
|
12
|
+
* eager_rule_load + skill_descriptions + command_descriptions + mcp_schemas
|
|
13
|
+
* Reduced: eager→thin rule projection, descriptions/schemas unchanged.
|
|
14
|
+
*
|
|
15
|
+
* Deterministic (reads the pinned baseline). Every emitted number binds to
|
|
16
|
+
* docs/CLAIMS.md with a method line (council Q4 hard condition).
|
|
17
|
+
*
|
|
18
|
+
* Usage: benchmark.ts [--baseline PATH] [--format text|json]
|
|
19
|
+
* Exit codes: 0 ok, 1 baseline missing/malformed, 2 usage error.
|
|
20
|
+
*/
|
|
21
|
+
import * as fs from 'node:fs';
|
|
22
|
+
import * as path from 'node:path';
|
|
23
|
+
import process from 'node:process';
|
|
24
|
+
import { fileURLToPath } from 'node:url';
|
|
25
|
+
|
|
26
|
+
const PROG = 'benchmark.ts';
|
|
27
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
28
|
+
export const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
29
|
+
export const BASELINE = path.join(REPO_ROOT, 'internal', 'bench', 'reports', 'token-baseline.json');
|
|
30
|
+
|
|
31
|
+
interface Metrics {
|
|
32
|
+
eager_rule_load: number;
|
|
33
|
+
thin_rule_load: number;
|
|
34
|
+
skill_descriptions: number;
|
|
35
|
+
command_descriptions: number;
|
|
36
|
+
mcp_schemas: number;
|
|
37
|
+
}
|
|
38
|
+
export interface BenchmarkReport {
|
|
39
|
+
baseline_full_projection: number;
|
|
40
|
+
reduced_projection: number;
|
|
41
|
+
saved: number;
|
|
42
|
+
reduction_ratio: number; // 0..1
|
|
43
|
+
method: string;
|
|
44
|
+
components: Metrics;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function _num(v: unknown): number {
|
|
48
|
+
return typeof v === 'number' && Number.isFinite(v) ? v : 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Compute the reduction report from a token-baseline metrics object. */
|
|
52
|
+
export function computeReport(metrics: Metrics): BenchmarkReport {
|
|
53
|
+
const shared = metrics.skill_descriptions + metrics.command_descriptions + metrics.mcp_schemas;
|
|
54
|
+
const full = metrics.eager_rule_load + shared;
|
|
55
|
+
const reduced = metrics.thin_rule_load + shared;
|
|
56
|
+
const saved = full - reduced;
|
|
57
|
+
const ratio = full > 0 ? Math.round((saved / full) * 10000) / 10000 : 0;
|
|
58
|
+
return {
|
|
59
|
+
baseline_full_projection: full,
|
|
60
|
+
reduced_projection: reduced,
|
|
61
|
+
saved,
|
|
62
|
+
reduction_ratio: ratio,
|
|
63
|
+
method:
|
|
64
|
+
'Baseline = FULL always-loaded projection (eager rule load + skill/command ' +
|
|
65
|
+
'descriptions + MCP schemas) from token-baseline.json; reduced = thin rule ' +
|
|
66
|
+
'projection, shared surfaces unchanged. Not a synthetic strawman (council Q4).',
|
|
67
|
+
components: metrics,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function loadMetrics(baselinePath: string): Metrics {
|
|
72
|
+
const parsed = JSON.parse(fs.readFileSync(baselinePath, 'utf-8')) as { metrics?: Record<string, unknown> };
|
|
73
|
+
const m = parsed.metrics ?? {};
|
|
74
|
+
return {
|
|
75
|
+
eager_rule_load: _num(m['eager_rule_load']),
|
|
76
|
+
thin_rule_load: _num(m['thin_rule_load']),
|
|
77
|
+
skill_descriptions: _num(m['skill_descriptions']),
|
|
78
|
+
command_descriptions: _num(m['command_descriptions']),
|
|
79
|
+
mcp_schemas: _num(m['mcp_schemas']),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function main(argv: string[]): number {
|
|
84
|
+
let baselinePath = BASELINE;
|
|
85
|
+
let format: 'text' | 'json' = 'text';
|
|
86
|
+
for (let i = 0; i < argv.length; i++) {
|
|
87
|
+
const a = argv[i] as string;
|
|
88
|
+
if (a === '--baseline') baselinePath = argv[++i] ?? baselinePath;
|
|
89
|
+
else if (a === '--format') format = (argv[++i] as 'text' | 'json') ?? 'text';
|
|
90
|
+
else if (a === '-h' || a === '--help') {
|
|
91
|
+
process.stdout.write(`usage: ${PROG} [--baseline PATH] [--format text|json]\n`);
|
|
92
|
+
return 0;
|
|
93
|
+
} else {
|
|
94
|
+
process.stderr.write(`${PROG}: error: unknown argument ${a}\n`);
|
|
95
|
+
return 2;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
let report: BenchmarkReport;
|
|
99
|
+
try {
|
|
100
|
+
report = computeReport(loadMetrics(baselinePath));
|
|
101
|
+
} catch (exc) {
|
|
102
|
+
process.stderr.write(`${PROG}: cannot read baseline ${baselinePath}: ${String(exc)}\n`);
|
|
103
|
+
return 1;
|
|
104
|
+
}
|
|
105
|
+
if (format === 'json') {
|
|
106
|
+
process.stdout.write(JSON.stringify(report, null, 2) + '\n');
|
|
107
|
+
return 0;
|
|
108
|
+
}
|
|
109
|
+
const pct = (report.reduction_ratio * 100).toFixed(1);
|
|
110
|
+
process.stdout.write(
|
|
111
|
+
`context token reduction (vs full always-loaded projection):\n` +
|
|
112
|
+
` full projection ${report.baseline_full_projection} tokens\n` +
|
|
113
|
+
` reduced (thin) ${report.reduced_projection} tokens\n` +
|
|
114
|
+
` saved ${report.saved} tokens (${pct}%)\n` +
|
|
115
|
+
` method ${report.method}\n`,
|
|
116
|
+
);
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (fileURLToPath(import.meta.url) === path.resolve(process.argv[1] ?? '')) {
|
|
121
|
+
process.exit(main(process.argv.slice(2)));
|
|
122
|
+
}
|
|
@@ -26,6 +26,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
26
26
|
import { REPO, LEDGER_REL, load_ledger, pointer_unresolved } from './check_claims.js';
|
|
27
27
|
import { collectSkillGaps } from './check_skill_gaps.js';
|
|
28
28
|
import { loadRows as loadComparisonRows } from './check_comparison.js';
|
|
29
|
+
import { computeStatus as domainSoundnessStatus } from './domain_soundness_status.js';
|
|
29
30
|
|
|
30
31
|
const _FILE = fileURLToPath(import.meta.url);
|
|
31
32
|
const OUT_REL = 'docs/proof.md';
|
|
@@ -118,30 +119,42 @@ function render(): string {
|
|
|
118
119
|
L.push('checks the wrong property is worse than none), so the number grows');
|
|
119
120
|
L.push('deliberately, not overnight.');
|
|
120
121
|
L.push('');
|
|
122
|
+
const ds = domainSoundnessStatus();
|
|
121
123
|
L.push('**Non-coding domain soundness — scoped, not proven.** The `finance` /');
|
|
122
124
|
L.push('`founder` / `ops` / `content` profiles sell concrete domain value (DCF,');
|
|
123
125
|
L.push('runway, RICE, incident command, messaging), but the skills are forged on');
|
|
124
126
|
L.push('TS/PHP — "promising, not proven" off those stacks. A disclaimer floor');
|
|
125
127
|
L.push('bounds *liability*, not *correctness*: a skill can be format-correct,');
|
|
126
|
-
L.push(
|
|
128
|
+
L.push(`disclaimered, and still embed a wrong domain assumption. Today **${ds.validated} of ${ds.total}**`);
|
|
127
129
|
L.push('default-surface domain skills carry a sourced `domain-truth` fixture');
|
|
128
|
-
L.push('(`./scripts-run src/scripts/domain_soundness_status`);
|
|
129
|
-
L.push('`unvalidated` and the validated count is CI-ratcheted.
|
|
130
|
-
L.push('
|
|
131
|
-
L.push('
|
|
132
|
-
L.push('
|
|
130
|
+
L.push('(`./scripts-run src/scripts/domain_soundness_status`); the rest are labeled');
|
|
131
|
+
L.push('`unvalidated` and the validated count is CI-ratcheted. The fixtures landed');
|
|
132
|
+
L.push('so far are the **deterministic** targets (runway, unit-economics, DCF,');
|
|
133
|
+
L.push('forecasting, scenario band/sensitivity), whose answer keys are computed');
|
|
134
|
+
L.push('from cited standard formulas — never the skill\'s own output; the');
|
|
135
|
+
L.push('**rubric** targets (incident command, messaging, fundraising, editorial)');
|
|
136
|
+
L.push('need domain-competent grounding and remain unvalidated, so validation');
|
|
137
|
+
L.push('lands deliberately — the gap is published, never implied away.');
|
|
133
138
|
L.push('');
|
|
134
|
-
L.push('**Second-brain substrate —
|
|
135
|
-
L.push('
|
|
136
|
-
L.push('
|
|
137
|
-
L.push('
|
|
138
|
-
L.push('
|
|
139
|
-
L.push('
|
|
140
|
-
L.push('
|
|
141
|
-
L.push('
|
|
142
|
-
L.push('
|
|
143
|
-
L.push('
|
|
144
|
-
L.push('
|
|
139
|
+
L.push('**Second-brain substrate — measured recall lift, honestly bounded.** On a');
|
|
140
|
+
L.push('deterministic multi-session recall corpus, the memory substrate beats a');
|
|
141
|
+
L.push('no-memory baseline AND an equal-byte placebo: memory-on **27/27** vs');
|
|
142
|
+
L.push('no-memory **10/27** vs placebo **9/27** (claude-haiku-4-5, 9 tasks × 3');
|
|
143
|
+
L.push('seeds, sign test **p = 0.031** for both pairings) — a real,');
|
|
144
|
+
L.push('placebo-controlled lift (`internal/bench/reports/second-brain-delta.json`).');
|
|
145
|
+
L.push('Scoped, not oversold: this is the context-value upper bound (perfect');
|
|
146
|
+
L.push('retrieval on a one-fact-per-task corpus), NOT retrieval precision under a');
|
|
147
|
+
L.push('large store; the lift concentrates exactly where memory is the only source');
|
|
148
|
+
L.push('and ties where the prompt self-contains the fact. Boundary vs a human PKM,');
|
|
149
|
+
L.push('and why the Obsidian export stays declined, are in');
|
|
150
|
+
L.push('[`docs/second-brain-scope.md`](second-brain-scope.md).');
|
|
151
|
+
L.push('');
|
|
152
|
+
L.push('A follow-up removed the perfect-retrieval assumption: against a store of');
|
|
153
|
+
L.push('keyword-overlapping confusers the REAL retrieval recalls the needed');
|
|
154
|
+
L.push('decision into the top-5 (9/9) and the model disambiguates it — retrieval-on');
|
|
155
|
+
L.push('**27/27** vs no-memory **5/27** and vs placebo **5/27** (p=0.008 both). The');
|
|
156
|
+
L.push('honest limit: the keyword scorer recalls but does not rank (mean tie-set');
|
|
157
|
+
L.push('3.3), which is the SQLite-FTS5 activation signal (ADR-116) at scale.');
|
|
145
158
|
L.push('');
|
|
146
159
|
L.push('## 3. Known limits (published, witness-tested)');
|
|
147
160
|
L.push('');
|
|
@@ -108,6 +108,57 @@ export function evaluatePair(
|
|
|
108
108
|
return { id: task.id, winner, length_delta, winner_is_longer };
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Cohen's Kappa — chance-corrected agreement between two raters over the same
|
|
113
|
+
* categorical labels (road-to-retrieval-substrate-hardening B7b). 1 = perfect,
|
|
114
|
+
* 0 = chance-level, <0 = worse than chance. `NaN` when there is no data.
|
|
115
|
+
*/
|
|
116
|
+
export function cohensKappa(labelsA: readonly string[], labelsB: readonly string[]): number {
|
|
117
|
+
const n = Math.min(labelsA.length, labelsB.length);
|
|
118
|
+
if (n === 0) return Number.NaN;
|
|
119
|
+
const cats = new Set<string>();
|
|
120
|
+
for (let i = 0; i < n; i++) {
|
|
121
|
+
cats.add(labelsA[i] as string);
|
|
122
|
+
cats.add(labelsB[i] as string);
|
|
123
|
+
}
|
|
124
|
+
let observedAgree = 0;
|
|
125
|
+
const countA = new Map<string, number>();
|
|
126
|
+
const countB = new Map<string, number>();
|
|
127
|
+
for (let i = 0; i < n; i++) {
|
|
128
|
+
const a = labelsA[i] as string;
|
|
129
|
+
const b = labelsB[i] as string;
|
|
130
|
+
if (a === b) observedAgree += 1;
|
|
131
|
+
countA.set(a, (countA.get(a) ?? 0) + 1);
|
|
132
|
+
countB.set(b, (countB.get(b) ?? 0) + 1);
|
|
133
|
+
}
|
|
134
|
+
const po = observedAgree / n;
|
|
135
|
+
let pe = 0;
|
|
136
|
+
for (const c of cats) pe += ((countA.get(c) ?? 0) / n) * ((countB.get(c) ?? 0) / n);
|
|
137
|
+
if (pe === 1) return 1; // both raters unanimous on one label → perfect by convention
|
|
138
|
+
return Math.round(((po - pe) / (1 - pe)) * 10000) / 10000;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Second-independent-judge validation: Cohen's Kappa over the two judges'
|
|
143
|
+
* per-pair winner labels (aligned by task id). A LOW kappa means the win-rate
|
|
144
|
+
* verdict rests on an unreliable grader — the trustworthiness gap the
|
|
145
|
+
* token-saving live run surfaced (33% judge-inconsistency), distinct from the
|
|
146
|
+
* effect-significance the Wilcoxon/McNemar gates already test.
|
|
147
|
+
*/
|
|
148
|
+
export function judgeKappa(resultsA: readonly PairResult[], resultsB: readonly PairResult[]): number {
|
|
149
|
+
const byIdB = new Map(resultsB.map((r) => [r.id, r.winner]));
|
|
150
|
+
const a: string[] = [];
|
|
151
|
+
const b: string[] = [];
|
|
152
|
+
for (const r of resultsA) {
|
|
153
|
+
const bw = byIdB.get(r.id);
|
|
154
|
+
if (bw !== undefined) {
|
|
155
|
+
a.push(r.winner);
|
|
156
|
+
b.push(bw);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return cohensKappa(a, b);
|
|
160
|
+
}
|
|
161
|
+
|
|
111
162
|
export interface QualityAggregate {
|
|
112
163
|
total: number;
|
|
113
164
|
thin_wins: number;
|