@event4u/agent-config 14.21.0 → 14.22.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/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +198 -0
- package/CREDITS.md +37 -0
- package/dist/agent-src/commands/analyze/inbox.md +380 -5
- package/dist/agent-src/commands/analyze/repo.md +4 -2
- package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +69 -0
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +5 -5
- package/dist/agent-src/rules/external-code-graph-interop.md +19 -14
- package/dist/agent-src/rules/token-efficiency.md +26 -3
- package/dist/agent-src/scripts/_cli_entry.ts +51 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +14 -23
- package/dist/agent-src/scripts/roadmap_gates.ts +4 -20
- package/dist/agent-src/scripts/roadmap_set_step.ts +5 -2
- package/dist/agent-src/scripts/stubs_due.ts +4 -18
- package/dist/agent-src/scripts/update_roadmap_progress.ts +7 -24
- package/dist/agent-src/skills/code-intelligence/SKILL.md +25 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +17 -0
- package/dist/agent-src/templates/agent-settings.md +0 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli-delegate/_cli_entry.js +8 -0
- package/dist/cli-delegate/_lib/pin_resolver.js +170 -0
- package/dist/cli-delegate/archival_sweep.js +8 -0
- package/dist/cli-delegate/archive_completed_roadmaps.js +494 -0
- package/dist/cli-delegate/check_update_banner.js +923 -0
- package/dist/cli-delegate/chunk-5EK5WQN6.js +28 -0
- package/dist/cli-delegate/chunk-7XGD2OEU.js +96 -0
- package/dist/cli-delegate/{chunk-643Z7FIC.js → chunk-BIXEDQPZ.js} +6 -0
- package/dist/cli-delegate/chunk-DBH4AHDK.js +155 -0
- package/dist/cli-delegate/chunk-JO25G5H7.js +20 -0
- package/dist/cli-delegate/chunk-LTYVIY76.js +7961 -0
- package/dist/cli-delegate/chunk-QAJS5YWY.js +999 -0
- package/dist/cli-delegate/{chunk-ZVBOHSVF.js → chunk-TGIPZBCW.js} +1 -1
- package/dist/cli-delegate/{chunk-YS6XTLEK.js → chunk-TYSY33MD.js} +56 -6
- package/dist/cli-delegate/chunk-ULET4BYS.js +328 -0
- package/dist/cli-delegate/chunk-YDQSHTD7.js +187 -0
- package/dist/cli-delegate/chunk-YWVXLR44.js +48 -0
- package/dist/cli-delegate/chunk-ZZQBUHFK.js +273 -0
- package/dist/cli-delegate/cmd_conformance.js +2 -2
- package/dist/cli-delegate/cmd_export.js +1 -1
- package/dist/cli-delegate/cmd_handoff.js +26 -2
- package/dist/cli-delegate/cmd_prune.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +2 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +1 -1
- package/dist/cli-delegate/cmd_uninstall.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +1 -1
- package/dist/cli-delegate/dashboard_mode.js +16 -0
- package/dist/cli-delegate/gate_budget.js +24 -0
- package/dist/cli-delegate/gate_execute.js +25 -0
- package/dist/cli-delegate/guarded_baseline.js +18 -0
- package/dist/cli-delegate/resume_probe.js +22 -0
- package/dist/cli-delegate/roadmap_gates.js +638 -0
- package/dist/cli-delegate/roadmap_set_step.js +173 -0
- package/dist/cli-delegate/stub_queue.js +47 -0
- package/dist/cli-delegate/stubs_due.js +38 -0
- package/dist/cli-delegate/ticket_bundles.js +59 -0
- package/dist/cli-delegate/update_roadmap_progress.js +60 -0
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +8 -8
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +1193 -917
- package/dist/install/claudeLayerCarriage.js +237 -0
- package/dist/install/claudePathsPlan.js +79 -0
- package/dist/install/hostLayerFingerprint.js +1 -1
- package/dist/install/install.mjs +6 -0
- package/dist/install/partitionEligibility.js +231 -117
- package/dist/install/ruleLayerPartition.js +10 -7
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +39219 -38995
- package/dist/scripts/condense.js +32 -25
- package/docs/MIGRATION.md +49 -1
- package/docs/contracts/settings-classes.md +1 -1
- package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +73 -0
- package/package.json +7 -4
- package/src/agent-src/scripts/_cli_entry.ts +51 -0
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +14 -23
- package/src/agent-src/scripts/roadmap_gates.ts +4 -20
- package/src/agent-src/scripts/roadmap_set_step.ts +5 -2
- package/src/agent-src/scripts/stubs_due.ts +4 -18
- package/src/agent-src/scripts/update_roadmap_progress.ts +7 -24
- package/src/config/continuity-surface.json +509 -0
- package/src/config/estate-count-budget.json +1 -1
- package/src/config/gate-coverage.yml +29 -8
- package/src/config/gate-violation-baselines.json +8 -8
- package/src/config/gitignore-block.txt +19 -0
- package/src/config/host-injection-effect.json +73 -0
- package/src/config/packed-binary-manifest.json +33 -0
- package/src/config/preamble-payload-budget.json +112 -105
- package/src/config/publish-surface.json +4 -2
- package/src/install/claudeLayerCarriage.ts +273 -0
- package/src/install/claudePathsPlan.ts +76 -0
- package/src/install/hostLayerFingerprint.ts +1 -1
- package/src/install/partitionEligibility.ts +261 -127
- package/src/install/ruleLayerPartition.ts +19 -23
- package/src/scripts/_dispatch.bash +77 -27
- package/src/scripts/_lib/auto_dispatch.ts +2 -2
- package/src/scripts/_lib/catalogue_layer_parity.ts +146 -0
- package/src/scripts/_lib/continuity_surface.ts +296 -0
- package/src/scripts/_lib/corpus_manifest.ts +26 -9
- package/src/scripts/_lib/hook_settings.ts +1 -1
- package/src/scripts/_lib/injection_effect.ts +151 -0
- package/src/scripts/_lib/layer_overlap_notice.ts +50 -6
- package/src/scripts/_lib/packed_binary_predicate.ts +225 -0
- package/src/scripts/_lib/pin_resolver.ts +13 -0
- package/src/scripts/_lib/regression_neighbourhood.ts +1 -1
- package/src/scripts/_lib/retired_status.ts +74 -0
- package/src/scripts/_lib/skill_catalogue.ts +156 -21
- package/src/scripts/_lib/subagent_capsule.ts +117 -8
- package/src/scripts/ai_council/api_transport.ts +5 -0
- package/src/scripts/ai_council/deliberation_metrics.ts +353 -0
- package/src/scripts/ai_council/prompts.ts +9 -0
- package/src/scripts/ai_council/synthesis_disagreement.ts +206 -0
- package/src/scripts/capture_skill_catalogue.ts +24 -11
- package/src/scripts/check_candidate_lines.ts +499 -0
- package/src/scripts/check_continuity_surface.ts +475 -0
- package/src/scripts/check_dependency_floors.ts +13 -6
- package/src/scripts/check_estate_count.ts +40 -79
- package/src/scripts/check_pack_size.ts +262 -23
- package/src/scripts/check_roadmap_trackable.ts +19 -16
- package/src/scripts/check_rule_layer_partition.ts +54 -34
- package/src/scripts/check_single_delivery.ts +97 -31
- package/src/scripts/check_update_banner.ts +13 -0
- package/src/scripts/code_graph/build.ts +138 -15
- package/src/scripts/code_graph/cli.ts +76 -8
- package/src/scripts/code_graph/extract.ts +11 -0
- package/src/scripts/code_graph/grammar_probe.ts +43 -0
- package/src/scripts/code_graph/loader.ts +88 -14
- package/src/scripts/code_graph/query.ts +121 -11
- package/src/scripts/code_graph/resolution_tiers.ts +238 -0
- package/src/scripts/code_graph/sqlite_store.ts +247 -6
- package/src/scripts/code_graph/types.ts +66 -1
- package/src/scripts/code_graph/validate.ts +23 -0
- package/src/scripts/condense.ts +34 -22
- package/src/scripts/corpus_manifest.ts +5 -5
- package/src/scripts/council_cli.ts +11 -12
- package/src/scripts/count_intent_disagreement.ts +426 -0
- package/src/scripts/handoff_context_hook.ts +79 -0
- package/src/scripts/hook_effect_doctor.ts +82 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +64 -12
- package/src/scripts/hooks/chain_nudge_hook.ts +282 -0
- package/src/scripts/hooks/code_graph_context_hook.ts +212 -0
- package/src/scripts/hooks/concern_registry.ts +4 -2
- package/src/scripts/hooks/host_semantics.ts +1 -1
- package/src/scripts/hooks/orchestration_record_hook.ts +2 -2
- package/src/scripts/hooks/rule_inject_hook.ts +1 -1
- package/src/scripts/hooks/skill_route_hook.ts +11 -4
- package/src/scripts/inbox_source_census.ts +322 -0
- package/src/scripts/install-hooks.sh +82 -0
- package/src/scripts/{lint_carrier_integrity.ts → lint_deferral_integrity.ts} +60 -214
- package/src/scripts/lint_plan_risk_register.ts +13 -17
- package/src/scripts/mcp_server/suggest_skill.ts +72 -0
- package/src/scripts/mcp_server/tools.ts +2 -56
- package/src/scripts/media/lib/adapter-contract.md +7 -2
- package/src/scripts/media/lib/fixtures/flux/asset-0001.png +0 -0
- package/src/scripts/media/lib/fixtures/gemini-image/asset-0001.png +0 -0
- package/src/scripts/media/lib/fixtures/ideogram/asset-0001.png +0 -0
- package/src/scripts/rdp_candidates_delta.ts +276 -0
- package/src/scripts/rdp_quality_eval.ts +217 -19
- package/src/scripts/release_mix_taxonomy.json +3 -2
- package/src/scripts/report_host_injection_effect.ts +189 -0
- package/src/scripts/report_layer_overlap.ts +41 -0
- package/src/scripts/report_skill_menu_census.ts +341 -0
- package/src/scripts/skill_tools/score_skill_relevance.ts +70 -6
- package/src/scripts/skill_tools/suggest_skill_for_task.ts +37 -3
- package/src/vendor/grammars/README.md +61 -0
- package/src/vendor/grammars/tree-sitter-javascript.wasm +0 -0
- package/src/vendor/grammars/tree-sitter-php.wasm +0 -0
- package/src/vendor/grammars/tree-sitter-typescript.wasm +0 -0
- package/src/scripts/_lib/carrier_status.ts +0 -118
- package/src/scripts/_lib/estate_carrier_cases.ts +0 -72
- package/src/scripts/hooks/code_graph_nudge_hook.ts +0 -191
|
@@ -143,10 +143,68 @@ export const CAPSULE_SCHEMA_VERSION = 4;
|
|
|
143
143
|
*/
|
|
144
144
|
export const ACCEPTED_CAPSULE_VERSIONS: readonly number[] = [3, 4];
|
|
145
145
|
|
|
146
|
-
/** The
|
|
147
|
-
export const CAPSULE_VARIANTS = ['worker', 'main_session'] as const;
|
|
146
|
+
/** The three CHECKPOINT variants sharing this schema. */
|
|
147
|
+
export const CAPSULE_VARIANTS = ['worker', 'main_session', 'continuity_record'] as const;
|
|
148
148
|
export type CapsuleVariant = (typeof CAPSULE_VARIANTS)[number];
|
|
149
149
|
|
|
150
|
+
/**
|
|
151
|
+
* `continuity_record` — the DETERMINISTIC main-session variant.
|
|
152
|
+
*
|
|
153
|
+
* Why a variant and not a relaxed `main_session`. `main_session` requires
|
|
154
|
+
* `failed_approaches` and `successful_approaches` with at least one entry each,
|
|
155
|
+
* and requires the explicit token `none` when nothing failed or worked — the
|
|
156
|
+
* whole point of those fields being that silence and "nothing was abandoned"
|
|
157
|
+
* stay distinguishable. A writer that runs without model spend cannot know
|
|
158
|
+
* either. It could only write `none`, which asserts a judgement nobody made.
|
|
159
|
+
*
|
|
160
|
+
* The AI council of 2026-09-07 ruled exactly this: add a variant rather than
|
|
161
|
+
* stuffing absence markers into `main_session`, because explicit absence
|
|
162
|
+
* markers "misrepresent unavailable model judgments as values". This constant
|
|
163
|
+
* set is that ruling in code.
|
|
164
|
+
*
|
|
165
|
+
* The fields below are FORBIDDEN on this variant rather than optional, and the
|
|
166
|
+
* distinction is load-bearing. Optional would leave the door open for a later
|
|
167
|
+
* writer to fill one with a guess and for a reader to be unable to tell a
|
|
168
|
+
* derived record from a judged one. Forbidden makes the variant's meaning
|
|
169
|
+
* checkable: everything a `continuity_record` carries was computed from disk.
|
|
170
|
+
*
|
|
171
|
+
* Nothing here is invented — every name is an existing key of this same schema.
|
|
172
|
+
* The council's constraint was explicit: the required fields "must be derived
|
|
173
|
+
* from the existing validated envelope contract; this ruling does not authorize
|
|
174
|
+
* inventing parallel identity or directive fields."
|
|
175
|
+
*
|
|
176
|
+
* Version compatibility is unchanged and envelope-wide: a `continuity_record`
|
|
177
|
+
* declares the same `capsule_version`, and a reader that does not know the
|
|
178
|
+
* variant refuses it by name (see `validateRecycleEnvelope`) rather than
|
|
179
|
+
* guessing at its required set.
|
|
180
|
+
*/
|
|
181
|
+
export const CONTINUITY_RECORD_FORBIDDEN_KEYS: ReadonlySet<string> = new Set([
|
|
182
|
+
'decisions',
|
|
183
|
+
'constraints',
|
|
184
|
+
'do_not_touch',
|
|
185
|
+
'assumptions',
|
|
186
|
+
'failed_approaches',
|
|
187
|
+
'successful_approaches',
|
|
188
|
+
'open_questions',
|
|
189
|
+
'not_carried_forward',
|
|
190
|
+
'status_summary',
|
|
191
|
+
'suggested_skills',
|
|
192
|
+
'open_worker_envelopes',
|
|
193
|
+
]);
|
|
194
|
+
|
|
195
|
+
/** Required on a `continuity_record` — each one computable from on-disk state. */
|
|
196
|
+
export const CONTINUITY_RECORD_REQUIRED_KEYS: readonly string[] = [
|
|
197
|
+
'capsule_version',
|
|
198
|
+
'variant',
|
|
199
|
+
'summary',
|
|
200
|
+
'task',
|
|
201
|
+
'workspace',
|
|
202
|
+
'written_at',
|
|
203
|
+
'acceptance_criteria',
|
|
204
|
+
'remaining',
|
|
205
|
+
'predecessor',
|
|
206
|
+
];
|
|
207
|
+
|
|
150
208
|
/** A single-line token no longer than `max` chars. Rejects any body-shaped value. */
|
|
151
209
|
function isShortLine(value: unknown, max: number): value is string {
|
|
152
210
|
return typeof value === 'string' && value.length > 0 && value.length <= max && !value.includes('\n');
|
|
@@ -757,6 +815,31 @@ export function validateRecycleEnvelope(input: unknown): string[] {
|
|
|
757
815
|
}
|
|
758
816
|
}
|
|
759
817
|
|
|
818
|
+
// VARIANT FIRST, then version. The order is the contract, not a style
|
|
819
|
+
// choice. Required fields are variant-specific, so a reader that does not
|
|
820
|
+
// recognise the variant cannot know what to demand — and a reader that
|
|
821
|
+
// reports twenty field violations against a variant it never understood has
|
|
822
|
+
// told the operator nothing useful and, worse, has implied the record was
|
|
823
|
+
// malformed when the truth is that the reader is old. One refusal naming
|
|
824
|
+
// the variant is the honest output, and it is what makes an old reader
|
|
825
|
+
// meeting a new record a legible event rather than a pile of noise.
|
|
826
|
+
const variant = e['variant'];
|
|
827
|
+
if (typeof variant !== 'string' || !(CAPSULE_VARIANTS as readonly string[]).includes(variant)) {
|
|
828
|
+
return [
|
|
829
|
+
`unknown variant ${JSON.stringify(variant)} — this reader knows ` +
|
|
830
|
+
`${CAPSULE_VARIANTS.join(' | ')}. A reader that does not know a variant must not ` +
|
|
831
|
+
'guess at its required fields, so no further field checks were run.',
|
|
832
|
+
];
|
|
833
|
+
}
|
|
834
|
+
if (variant === 'worker') {
|
|
835
|
+
return [
|
|
836
|
+
"variant 'worker' is a subagent generation handover and is validated by the worker " +
|
|
837
|
+
'path, not by the main-session recycle validator — routing it here would apply ' +
|
|
838
|
+
'the wrong required set to a well-formed record.',
|
|
839
|
+
];
|
|
840
|
+
}
|
|
841
|
+
const isDerived = variant === 'continuity_record';
|
|
842
|
+
|
|
760
843
|
const version = e['capsule_version'];
|
|
761
844
|
if (typeof version !== 'number' || !ACCEPTED_CAPSULE_VERSIONS.includes(version)) {
|
|
762
845
|
errors.push(`capsule_version must be one of ${ACCEPTED_CAPSULE_VERSIONS.join(' | ')}`);
|
|
@@ -766,9 +849,35 @@ export function validateRecycleEnvelope(input: unknown): string[] {
|
|
|
766
849
|
// Demanding the new fields of an already-written record would be a
|
|
767
850
|
// retroactive requirement, which is the one thing the recorded schema lock
|
|
768
851
|
// forbids ("add fields or variants, never repurpose or remove one").
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
852
|
+
//
|
|
853
|
+
// A `continuity_record` is exempt from the two v4 judgement fields for the
|
|
854
|
+
// opposite reason: it is forbidden from carrying them at all (below), so
|
|
855
|
+
// requiring them would make the variant unsatisfiable.
|
|
856
|
+
const atV4 = version === 4 && !isDerived;
|
|
857
|
+
|
|
858
|
+
if (isDerived) {
|
|
859
|
+
// Forbidden, not merely absent: a derived record that carries a
|
|
860
|
+
// judgement field is either a `main_session` mislabelled, or a
|
|
861
|
+
// deterministic writer that invented a value. Both are worth refusing
|
|
862
|
+
// loudly, and neither is distinguishable after the fact.
|
|
863
|
+
for (const key of Object.keys(e)) {
|
|
864
|
+
if (CONTINUITY_RECORD_FORBIDDEN_KEYS.has(key)) {
|
|
865
|
+
errors.push(
|
|
866
|
+
`field "${key}" is forbidden on variant 'continuity_record' — it carries a ` +
|
|
867
|
+
'model judgement, and a record written without model spend cannot have made ' +
|
|
868
|
+
"one. Use variant 'main_session' for a judged record.",
|
|
869
|
+
);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
for (const key of CONTINUITY_RECORD_REQUIRED_KEYS) {
|
|
873
|
+
if (e[key] === undefined) {
|
|
874
|
+
errors.push(
|
|
875
|
+
`field "${key}" is required on variant 'continuity_record' — it is computable ` +
|
|
876
|
+
'from on-disk state, so its absence is a writer defect rather than an ' +
|
|
877
|
+
'unavailable judgement.',
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
772
881
|
}
|
|
773
882
|
if (!isShortLine(e['summary'], MAX_LINE_CHARS)) {
|
|
774
883
|
errors.push(`summary must be a single line of 1–${MAX_LINE_CHARS} chars`);
|
|
@@ -789,7 +898,7 @@ export function validateRecycleEnvelope(input: unknown): string[] {
|
|
|
789
898
|
errors.push('acceptance_criteria must carry at least one entry — the successor cannot know "done" without it');
|
|
790
899
|
}
|
|
791
900
|
checkList(errors, 'remaining', e['remaining'], MAX_LINE_CHARS, true);
|
|
792
|
-
checkList(errors, 'not_carried_forward', e['not_carried_forward'], MAX_LINE_CHARS,
|
|
901
|
+
checkList(errors, 'not_carried_forward', e['not_carried_forward'], MAX_LINE_CHARS, !isDerived);
|
|
793
902
|
checkList(errors, 'decisions', e['decisions'], MAX_LINE_CHARS, false);
|
|
794
903
|
errors.push(...decisionTagErrors(e['decisions']));
|
|
795
904
|
checkList(errors, 'constraints', e['constraints'], MAX_LINE_CHARS, false);
|
|
@@ -809,7 +918,7 @@ export function validateRecycleEnvelope(input: unknown): string[] {
|
|
|
809
918
|
|
|
810
919
|
// Required with ≥ 1 entry (Phase 2.3): a session that abandoned nothing
|
|
811
920
|
// writes `none`. Absence must never be readable as "nothing failed".
|
|
812
|
-
checkList(errors, 'failed_approaches', e['failed_approaches'], MAX_LINE_CHARS,
|
|
921
|
+
checkList(errors, 'failed_approaches', e['failed_approaches'], MAX_LINE_CHARS, !isDerived);
|
|
813
922
|
if (Array.isArray(e['failed_approaches']) && e['failed_approaches'].length === 0) {
|
|
814
923
|
errors.push(
|
|
815
924
|
'failed_approaches must carry at least one entry — write "none" explicitly, so ' +
|
|
@@ -835,7 +944,7 @@ export function validateRecycleEnvelope(input: unknown): string[] {
|
|
|
835
944
|
// is a reader waiting for something that will not arrive.
|
|
836
945
|
const predecessor = e['predecessor'];
|
|
837
946
|
if (predecessor === undefined) {
|
|
838
|
-
if (atV4) {
|
|
947
|
+
if (atV4 || isDerived) {
|
|
839
948
|
errors.push(
|
|
840
949
|
'predecessor is required — name the session this one continues, or write "none" ' +
|
|
841
950
|
'explicitly for the first session in a workspace',
|
|
@@ -47,6 +47,11 @@ export function curlJsonPost(url: string, extraHeaders: string[], body: unknown)
|
|
|
47
47
|
// before the `spawnSync` timeout would kill it with an opaque ETIMEDOUT.
|
|
48
48
|
// A full 16k-token generation legitimately runs several minutes, so the
|
|
49
49
|
// ceiling is 300s, not 120s (the old value timed out long Anthropic calls).
|
|
50
|
+
// Both ceilings are PER CALL, never a run total. Members are iterated
|
|
51
|
+
// serially, so a run's worst case is members x passes x 290s, and nothing
|
|
52
|
+
// cancels a call mid-flight: `spawnSync` takes no signal, which is the
|
|
53
|
+
// synchronous contract the module docblock states. The only run-level bound
|
|
54
|
+
// is the USD cap, and it is checked AFTER a call returns.
|
|
50
55
|
args.push('--connect-timeout', '30', '--max-time', '290');
|
|
51
56
|
args.push('-w', '\n%{http_code}', '--data-binary', '@-');
|
|
52
57
|
const r = spawnSync('curl', args, {
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two per-run deliberation measurements, both computed from outputs the run has
|
|
3
|
+
* already paid for: a per-correction stage attribution, and the
|
|
4
|
+
* `zero_marginal_value_call_rate`.
|
|
5
|
+
*
|
|
6
|
+
* Pure and offline. No model call, no new similarity measure — every judgement
|
|
7
|
+
* below reuses `is_near_duplicate` from `debate_gates.ts`, which is the
|
|
8
|
+
* repository's existing answer to "did this reply add anything", and the
|
|
9
|
+
* objection vocabulary that module already publishes. Forking a second
|
|
10
|
+
* similarity notion here would be two answers to one question, and the second
|
|
11
|
+
* is the one nobody updates.
|
|
12
|
+
*
|
|
13
|
+
* WHAT A CORRECTION IS, AND WHAT IT IS NOT.
|
|
14
|
+
*
|
|
15
|
+
* A correction is an objection-bearing sentence: one that carries a marker from
|
|
16
|
+
* {@link CORRECTION_MARKERS} and therefore names something it is pushing back
|
|
17
|
+
* on. Its FIRST STAGE is the earliest stage whose text contains that sentence
|
|
18
|
+
* or a near-duplicate of it — so a point raised in round 1 and repeated in
|
|
19
|
+
* round 3 is attributed to round 1, which is the whole question 10.2 asks.
|
|
20
|
+
*
|
|
21
|
+
* It decides ATTRIBUTION, never USEFULNESS. Whether a correction improved the
|
|
22
|
+
* verdict is a benchmark question and the benchmark has not run; a mechanism
|
|
23
|
+
* here that claimed to score usefulness would be inventing the number the
|
|
24
|
+
* forbidden-claims list exists to prevent. {@link attributeCorrections} answers
|
|
25
|
+
* "where did this first appear", and the word "useful" in the step it serves is
|
|
26
|
+
* a filter a human applies to the output, not one this code applies for them.
|
|
27
|
+
*
|
|
28
|
+
* WHAT ZERO MARGINAL VALUE IS.
|
|
29
|
+
*
|
|
30
|
+
* A call has zero marginal value when its text near-duplicates something the
|
|
31
|
+
* run had ALREADY received when the call was made. Not "duplicates the member's
|
|
32
|
+
* own prior round" — a member restating a peer's answer verbatim adds exactly
|
|
33
|
+
* as little, and the per-member reading would score it as novel. So the
|
|
34
|
+
* comparison set is every successful call that preceded it in run order.
|
|
35
|
+
*
|
|
36
|
+
* The first successful call is never zero-marginal: nothing preceded it. That
|
|
37
|
+
* makes the denominator at least 1 whenever the run produced any usable call,
|
|
38
|
+
* which is why the rate is a number rather than a `null` on every real run —
|
|
39
|
+
* and a run that produced NO usable call gets `null` with a stated reason,
|
|
40
|
+
* because zero calls is not a rate of zero.
|
|
41
|
+
*
|
|
42
|
+
* HONEST SCOPE. This measures textual novelty against an 0.8 Jaccard bar. It
|
|
43
|
+
* does not measure whether a call changed the verdict, and no consumer may read
|
|
44
|
+
* it as decision-quality evidence. The threshold is the shipped
|
|
45
|
+
* `NOVELTY_DUP_THRESHOLD`, inherited rather than tuned here; nothing has been
|
|
46
|
+
* benchmarked against it.
|
|
47
|
+
*/
|
|
48
|
+
import type { CouncilResponse } from './clients.js';
|
|
49
|
+
import { is_near_duplicate, NOVELTY_DUP_THRESHOLD } from './debate_gates.js';
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Objection markers. Deliberately the same vocabulary the shipped dissent
|
|
53
|
+
* counter uses, kept as its own constant because that one counts members and
|
|
54
|
+
* this one classifies sentences.
|
|
55
|
+
*/
|
|
56
|
+
export const CORRECTION_MARKERS =
|
|
57
|
+
/\b(disagree|disagrees|object|objection|reject|rejects|however|but|flaw|flawed|wrong|incorrect|counter|counter-position|dissent|contradict|contradicts|refute|refutes|mistaken|overstates|understates)\b/i;
|
|
58
|
+
|
|
59
|
+
/** One call the run made, in the order the run made it. */
|
|
60
|
+
export interface DeliberationCall {
|
|
61
|
+
/** Stage the call belongs to — a round label, a phase name, whatever the caller uses. */
|
|
62
|
+
readonly stage: string;
|
|
63
|
+
/** `provider/model`, or any stable member identity. */
|
|
64
|
+
readonly member: string;
|
|
65
|
+
/** The reply text. Empty or error replies are not scored. */
|
|
66
|
+
readonly text: string;
|
|
67
|
+
/** True when the transport reported a failure; such a call is not scored. */
|
|
68
|
+
readonly failed?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Where one correction first entered the deliberation. */
|
|
72
|
+
export interface CorrectionAttribution {
|
|
73
|
+
/** The correction sentence, trimmed and whitespace-collapsed. */
|
|
74
|
+
readonly correction: string;
|
|
75
|
+
/** Stage label of the earliest call carrying it. */
|
|
76
|
+
readonly firstStage: string;
|
|
77
|
+
/** Zero-based index of that call in run order. */
|
|
78
|
+
readonly firstCallIndex: number;
|
|
79
|
+
/** Member whose call first carried it. */
|
|
80
|
+
readonly firstMember: string;
|
|
81
|
+
/** How many later calls restated it. 0 means it was said once. */
|
|
82
|
+
readonly restatements: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* One deliberation stage and what it produced — the record shape
|
|
87
|
+
* `CouncilRouteRecord` carries, filled from a real run rather than by a test.
|
|
88
|
+
*/
|
|
89
|
+
export interface StageProduction {
|
|
90
|
+
readonly stage: string;
|
|
91
|
+
readonly produced: number;
|
|
92
|
+
readonly calls: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface ZeroMarginalValueReading {
|
|
96
|
+
/**
|
|
97
|
+
* Zero-marginal calls over scored calls, or `null` when the run produced no
|
|
98
|
+
* scorable call at all. `null` is "not measurable here", never zero.
|
|
99
|
+
*/
|
|
100
|
+
readonly rate: number | null;
|
|
101
|
+
/** Why the rate is `null`. Empty string when it is not. */
|
|
102
|
+
readonly unavailableReason: string;
|
|
103
|
+
/** Every call the caller passed, failed and empty ones included. */
|
|
104
|
+
readonly callsTotal: number;
|
|
105
|
+
/** Calls that carried usable text — the denominator. */
|
|
106
|
+
readonly callsScored: number;
|
|
107
|
+
/** Calls that near-duplicated something already received — the numerator. */
|
|
108
|
+
readonly callsZeroMarginal: number;
|
|
109
|
+
/** The Jaccard bar used, published so the number can be re-derived. */
|
|
110
|
+
readonly threshold: number;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Decimals the published rate is rounded to, so a JSON round-trip is exact. */
|
|
114
|
+
export const RATE_PRECISION = 6;
|
|
115
|
+
|
|
116
|
+
function _round(value: number): number {
|
|
117
|
+
const f = 10 ** RATE_PRECISION;
|
|
118
|
+
return Math.round(value * f) / f;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function _usable(c: DeliberationCall): boolean {
|
|
122
|
+
return c.failed !== true && c.text.trim().length > 0;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Split a reply into sentences. Crude on purpose: no NLP, no dependency. */
|
|
126
|
+
function _sentences(text: string): string[] {
|
|
127
|
+
return text
|
|
128
|
+
.split(/(?<=[.!?])\s+|\n+/)
|
|
129
|
+
.map((s) => s.replace(/\s+/g, ' ').trim())
|
|
130
|
+
.filter((s) => s.length > 0);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Correction sentences in one reply.
|
|
135
|
+
*
|
|
136
|
+
* A minimum length is applied because a bare "But." carries a marker and no
|
|
137
|
+
* position; the bar is characters rather than a judgement so it stays
|
|
138
|
+
* predictable.
|
|
139
|
+
*/
|
|
140
|
+
export const MIN_CORRECTION_CHARS = 25;
|
|
141
|
+
|
|
142
|
+
export function correctionSentences(text: string): string[] {
|
|
143
|
+
return _sentences(text).filter(
|
|
144
|
+
(s) => s.length >= MIN_CORRECTION_CHARS && CORRECTION_MARKERS.test(s),
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Attribute every correction to the earliest call that carried it.
|
|
150
|
+
*
|
|
151
|
+
* Calls are read in the order given, which must be run order — the attribution
|
|
152
|
+
* is only meaningful if "earlier in the list" means "earlier in the run".
|
|
153
|
+
*/
|
|
154
|
+
export function attributeCorrections(
|
|
155
|
+
calls: readonly DeliberationCall[],
|
|
156
|
+
threshold: number = NOVELTY_DUP_THRESHOLD,
|
|
157
|
+
): CorrectionAttribution[] {
|
|
158
|
+
const out: CorrectionAttribution[] = [];
|
|
159
|
+
const restatements: number[] = [];
|
|
160
|
+
|
|
161
|
+
calls.forEach((call, idx) => {
|
|
162
|
+
if (!_usable(call)) return;
|
|
163
|
+
for (const sentence of correctionSentences(call.text)) {
|
|
164
|
+
const seen = out.findIndex(
|
|
165
|
+
(a) =>
|
|
166
|
+
a.correction === sentence || is_near_duplicate(a.correction, sentence, threshold),
|
|
167
|
+
);
|
|
168
|
+
if (seen !== -1) {
|
|
169
|
+
restatements[seen] = (restatements[seen] ?? 0) + 1;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
restatements.push(0);
|
|
173
|
+
out.push({
|
|
174
|
+
correction: sentence,
|
|
175
|
+
firstStage: call.stage,
|
|
176
|
+
firstCallIndex: idx,
|
|
177
|
+
firstMember: call.member,
|
|
178
|
+
restatements: 0,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
return out.map((a, i) => ({ ...a, restatements: restatements[i] ?? 0 }));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Per-stage production, derived from the attribution so the two can never
|
|
188
|
+
* disagree: `produced` counts the corrections FIRST attributed to that stage.
|
|
189
|
+
*/
|
|
190
|
+
export function stageProduction(
|
|
191
|
+
calls: readonly DeliberationCall[],
|
|
192
|
+
attributions: readonly CorrectionAttribution[],
|
|
193
|
+
): StageProduction[] {
|
|
194
|
+
const order: string[] = [];
|
|
195
|
+
const callCount = new Map<string, number>();
|
|
196
|
+
for (const c of calls) {
|
|
197
|
+
if (!callCount.has(c.stage)) {
|
|
198
|
+
order.push(c.stage);
|
|
199
|
+
callCount.set(c.stage, 0);
|
|
200
|
+
}
|
|
201
|
+
callCount.set(c.stage, (callCount.get(c.stage) ?? 0) + 1);
|
|
202
|
+
}
|
|
203
|
+
const produced = new Map<string, number>();
|
|
204
|
+
for (const a of attributions) {
|
|
205
|
+
produced.set(a.firstStage, (produced.get(a.firstStage) ?? 0) + 1);
|
|
206
|
+
}
|
|
207
|
+
return order.map((stage) => ({
|
|
208
|
+
stage,
|
|
209
|
+
produced: produced.get(stage) ?? 0,
|
|
210
|
+
calls: callCount.get(stage) ?? 0,
|
|
211
|
+
}));
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* The `zero_marginal_value_call_rate` for one run.
|
|
216
|
+
*
|
|
217
|
+
* Every scored call is compared against every scored call that preceded it. The
|
|
218
|
+
* comparison is O(n squared) in the number of calls, which is fine: a council
|
|
219
|
+
* run is single digits of calls per round.
|
|
220
|
+
*/
|
|
221
|
+
export function zeroMarginalValueRate(
|
|
222
|
+
calls: readonly DeliberationCall[],
|
|
223
|
+
threshold: number = NOVELTY_DUP_THRESHOLD,
|
|
224
|
+
): ZeroMarginalValueReading {
|
|
225
|
+
const received: string[] = [];
|
|
226
|
+
let zero = 0;
|
|
227
|
+
|
|
228
|
+
for (const call of calls) {
|
|
229
|
+
if (!_usable(call)) continue;
|
|
230
|
+
const dup = received.some((prior) => is_near_duplicate(prior, call.text, threshold));
|
|
231
|
+
if (dup) zero += 1;
|
|
232
|
+
received.push(call.text);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (received.length === 0) {
|
|
236
|
+
return {
|
|
237
|
+
rate: null,
|
|
238
|
+
unavailableReason:
|
|
239
|
+
'no scorable call — every call failed or returned empty text, and zero calls is not a rate of zero',
|
|
240
|
+
callsTotal: calls.length,
|
|
241
|
+
callsScored: 0,
|
|
242
|
+
callsZeroMarginal: 0,
|
|
243
|
+
threshold,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
rate: _round(zero / received.length),
|
|
249
|
+
unavailableReason: '',
|
|
250
|
+
callsTotal: calls.length,
|
|
251
|
+
callsZeroMarginal: zero,
|
|
252
|
+
callsScored: received.length,
|
|
253
|
+
threshold,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Everything this module emits for one run, ready to serialise. */
|
|
258
|
+
export interface DeliberationMetrics {
|
|
259
|
+
readonly zero_marginal_value_call_rate: number | null;
|
|
260
|
+
readonly zero_marginal_value_unavailable_reason: string;
|
|
261
|
+
readonly calls_total: number;
|
|
262
|
+
readonly calls_scored: number;
|
|
263
|
+
readonly calls_zero_marginal: number;
|
|
264
|
+
readonly novelty_threshold: number;
|
|
265
|
+
readonly stage_outputs: readonly StageProduction[];
|
|
266
|
+
readonly correction_attributions: readonly CorrectionAttribution[];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/** Compute both measurements in one pass over the run's calls. */
|
|
270
|
+
export function measureDeliberation(
|
|
271
|
+
calls: readonly DeliberationCall[],
|
|
272
|
+
threshold: number = NOVELTY_DUP_THRESHOLD,
|
|
273
|
+
): DeliberationMetrics {
|
|
274
|
+
const attributions = attributeCorrections(calls, threshold);
|
|
275
|
+
const zmv = zeroMarginalValueRate(calls, threshold);
|
|
276
|
+
return {
|
|
277
|
+
zero_marginal_value_call_rate: zmv.rate,
|
|
278
|
+
zero_marginal_value_unavailable_reason: zmv.unavailableReason,
|
|
279
|
+
calls_total: zmv.callsTotal,
|
|
280
|
+
calls_scored: zmv.callsScored,
|
|
281
|
+
calls_zero_marginal: zmv.callsZeroMarginal,
|
|
282
|
+
novelty_threshold: zmv.threshold,
|
|
283
|
+
stage_outputs: stageProduction(calls, attributions),
|
|
284
|
+
correction_attributions: attributions,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* The parts of one council run that made provider calls, in the order the run
|
|
290
|
+
* made them.
|
|
291
|
+
*
|
|
292
|
+
* Assembled here rather than at the call site because "which calls did this run
|
|
293
|
+
* make, and at which stage" is this module's own subject, and the CLI had the
|
|
294
|
+
* list only as a flat array with the stage boundaries implicit in the order.
|
|
295
|
+
* Both readings come out of one walk, so the billing total and the attribution
|
|
296
|
+
* can never be computed over different sets.
|
|
297
|
+
*/
|
|
298
|
+
export interface RunCallParts {
|
|
299
|
+
/** The deliberation responses `consult()` returned. */
|
|
300
|
+
readonly deliberation: readonly CouncilResponse[];
|
|
301
|
+
readonly peerReview?: readonly CouncilResponse[] | null;
|
|
302
|
+
readonly consensusExtraction?: readonly CouncilResponse[] | null;
|
|
303
|
+
readonly consensusScoring?: readonly CouncilResponse[] | null;
|
|
304
|
+
readonly chairman?: CouncilResponse | null;
|
|
305
|
+
readonly stanceRepairs?: readonly CouncilResponse[] | null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** The six stage labels, in run order. */
|
|
309
|
+
export const RUN_STAGES = Object.freeze([
|
|
310
|
+
'deliberation',
|
|
311
|
+
'peer-review',
|
|
312
|
+
'consensus-extraction',
|
|
313
|
+
'consensus-scoring',
|
|
314
|
+
'chairman',
|
|
315
|
+
'stance-repair',
|
|
316
|
+
] as const);
|
|
317
|
+
|
|
318
|
+
export interface RunCalls {
|
|
319
|
+
/** Stage-labelled calls, for {@link measureDeliberation}. */
|
|
320
|
+
readonly calls: readonly DeliberationCall[];
|
|
321
|
+
/** The same calls as raw responses, in the same order, for cost aggregation. */
|
|
322
|
+
readonly responses: CouncilResponse[];
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function _asCall(stage: string, r: CouncilResponse): DeliberationCall {
|
|
326
|
+
return {
|
|
327
|
+
stage,
|
|
328
|
+
member: `${r.provider}/${r.model}`,
|
|
329
|
+
text: r.text,
|
|
330
|
+
failed: r.error !== null && r.error !== undefined,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** Walk one run's parts into stage-labelled calls plus the flat response list. */
|
|
335
|
+
export function runCallsFrom(parts: RunCallParts): RunCalls {
|
|
336
|
+
const groups: ReadonlyArray<readonly [string, readonly CouncilResponse[]]> = [
|
|
337
|
+
['deliberation', parts.deliberation],
|
|
338
|
+
['peer-review', parts.peerReview ?? []],
|
|
339
|
+
['consensus-extraction', parts.consensusExtraction ?? []],
|
|
340
|
+
['consensus-scoring', parts.consensusScoring ?? []],
|
|
341
|
+
['chairman', parts.chairman === null || parts.chairman === undefined ? [] : [parts.chairman]],
|
|
342
|
+
['stance-repair', parts.stanceRepairs ?? []],
|
|
343
|
+
];
|
|
344
|
+
const calls: DeliberationCall[] = [];
|
|
345
|
+
const responses: CouncilResponse[] = [];
|
|
346
|
+
for (const [stage, group] of groups) {
|
|
347
|
+
for (const r of group) {
|
|
348
|
+
calls.push(_asCall(stage, r));
|
|
349
|
+
responses.push(r);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return { calls, responses };
|
|
353
|
+
}
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
import { wrapUntrustedBlocks } from '../_lib/untrusted_content.js';
|
|
26
26
|
|
|
27
27
|
import type { ProjectContext } from './project_context.js';
|
|
28
|
+
import { RESOLVING_EVIDENCE_SECTION } from './synthesis_disagreement.js';
|
|
28
29
|
|
|
29
30
|
// Python: NEUTRALITY_PREAMBLE = """\...""".strip()
|
|
30
31
|
export const NEUTRALITY_PREAMBLE = `You are an independent reviewer. You have NOT seen any prior reasoning,
|
|
@@ -302,6 +303,8 @@ read it directly from a response.
|
|
|
302
303
|
A single sentence: which course the host agent should advise the
|
|
303
304
|
user to take, grounded in the strongest converged point.
|
|
304
305
|
|
|
306
|
+
${RESOLVING_EVIDENCE_SECTION}
|
|
307
|
+
|
|
305
308
|
### Kill criteria
|
|
306
309
|
Observable conditions that would falsify this recommendation without
|
|
307
310
|
re-convening the council. Each entry names a threshold or a specific
|
|
@@ -329,6 +332,8 @@ and the host agent confirms are load-bearing. Maximum five.
|
|
|
329
332
|
APPROVE / REQUEST_CHANGES / REJECT and a single sentence justifying
|
|
330
333
|
the verdict, anchored on the strongest consensus or must-fix line.
|
|
331
334
|
|
|
335
|
+
${RESOLVING_EVIDENCE_SECTION}
|
|
336
|
+
|
|
332
337
|
### Kill criteria
|
|
333
338
|
Observable conditions that would falsify this verdict without re-convening
|
|
334
339
|
the council (a failing check, a missing test, a regression signal). Each
|
|
@@ -363,6 +368,8 @@ separate certainty word for that; \`evidence:\` measures how well a
|
|
|
363
368
|
finding is supported, and being alone in raising it is a different
|
|
364
369
|
property the section already carries.
|
|
365
370
|
|
|
371
|
+
${RESOLVING_EVIDENCE_SECTION}
|
|
372
|
+
|
|
366
373
|
### Kill criteria
|
|
367
374
|
Observable conditions that would falsify the analysis's leading finding
|
|
368
375
|
without re-convening the council. Each entry names a threshold or event.
|
|
@@ -381,6 +388,8 @@ export const CREATIVE_SYNTHESIS = `Write a free-form convergence / divergence
|
|
|
381
388
|
synthesis of the council in prose — no fixed section shape for the body.
|
|
382
389
|
Then close with exactly these two sections:
|
|
383
390
|
|
|
391
|
+
${RESOLVING_EVIDENCE_SECTION}
|
|
392
|
+
|
|
384
393
|
### Kill criteria
|
|
385
394
|
Observable conditions that would falsify the direction you converged on,
|
|
386
395
|
without re-convening the council. Each entry names a threshold or event.
|