@event4u/agent-config 16.1.0 → 16.2.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 +237 -248
- package/dist/agent-src/commands/roadmap/process-full.md +1 -1
- package/dist/agent-src/commands/work.md +4 -0
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +4 -8
- package/dist/agent-src/rules/ui-audit-gate.md +1 -2
- package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
- package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +2 -2
- package/dist/agent-src/skills/design-review/SKILL.md +30 -4
- package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +16 -1
- package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
- package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
- package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +23 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +30 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/dist/cli/python/knowledge_ingest.js +1 -1
- package/dist/cli/python/workspace_drive.js +1 -1
- package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-4XFP2Y3E.js} +1 -1
- package/dist/cli-delegate/{chunk-HDVVGPQW.js → chunk-7XGBX6ZH.js} +471 -387
- package/dist/cli-delegate/{chunk-GJCBB2UG.js → chunk-FP36SWJD.js} +432 -144
- package/dist/cli-delegate/{chunk-UNCRJBT3.js → chunk-IGD6ERX6.js} +31 -0
- package/dist/cli-delegate/{chunk-FXYUYU4N.js → chunk-JNMVLWLR.js} +42 -1
- package/dist/cli-delegate/{chunk-QJWAUMJW.js → chunk-YJ72UIAK.js} +730 -15
- package/dist/cli-delegate/cmd_conformance.js +902 -782
- package/dist/cli-delegate/cmd_doctor.js +2 -2
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +4 -4
- package/dist/cli-delegate/cmd_memory_get.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_route_audit.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +2 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +2 -2
- package/dist/cli-delegate/cmd_uninstall.js +2 -2
- package/dist/cli-delegate/cmd_validate.js +2 -2
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +10 -10
- 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 +6666 -2581
- package/dist/hooks/dispatch.sha256 +1 -0
- package/dist/install/install.mjs +848 -733
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +2747 -1510
- package/dist/server/schemas/settings.js +23 -0
- package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
- package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
- package/dist/ui/index.html +2 -2
- package/docs/contracts/settings-classes.md +6 -4
- package/package.json +4 -4
- package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
- package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/src/config/agent-settings.template.yml +29 -0
- package/src/config/ci-local-parity.yml +18 -0
- package/src/config/continuity-surface.json +2 -2
- package/src/config/gate-coverage.yml +234 -3
- package/src/config/gate-violation-baselines.json +7 -3
- package/src/config/hook-latency-budget.json +21 -0
- package/src/config/hook-token-budget.json +2 -2
- package/src/config/metric-registry.yml +114 -0
- package/src/config/placeholder-drift-budget.json +10 -0
- package/src/config/routing-coverage-seed.json +18 -4
- package/src/scripts/_cli/cmd_doctor.ts +5 -19
- package/src/scripts/_cli/doctor_execution.ts +100 -17
- package/src/scripts/_cli/doctor_network_posture.ts +226 -0
- package/src/scripts/_lib/body_portable.ts +102 -0
- package/src/scripts/_lib/cc_transcript.ts +132 -1
- package/src/scripts/_lib/concern_sla_window.ts +200 -0
- package/src/scripts/_lib/forge_protection.ts +114 -0
- package/src/scripts/_lib/forge_reader.ts +558 -0
- package/src/scripts/_lib/host_env_write.ts +111 -0
- package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
- package/src/scripts/_lib/obligations.ts +119 -8
- package/src/scripts/_lib/probe_inputs.ts +271 -0
- package/src/scripts/_lib/read_surface_scan.ts +322 -0
- package/src/scripts/_lib/release_highlights.ts +2 -1
- package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
- package/src/scripts/_lib/shell_write_shapes.ts +108 -0
- package/src/scripts/_lib/structural_hiding.ts +622 -0
- package/src/scripts/_lib/turn_end_refusals.ts +525 -4
- package/src/scripts/_lib/turn_end_transcript.ts +216 -0
- package/src/scripts/_lib/verification_command.ts +324 -0
- package/src/scripts/_lib/verification_evidence.ts +647 -0
- package/src/scripts/_lib/verify_clause.ts +280 -0
- package/src/scripts/ai_team/team_dispatch.ts +4 -0
- package/src/scripts/before_complete_hook.ts +340 -23
- package/src/scripts/bench_hook_latency.ts +412 -5
- package/src/scripts/cache_realization_report.ts +61 -1
- package/src/scripts/check_argument_hint.ts +287 -0
- package/src/scripts/check_claims.ts +125 -1
- package/src/scripts/check_enforcement_coverage.ts +38 -9
- package/src/scripts/check_enforcement_matrix.ts +245 -3
- package/src/scripts/check_finding_dispositions.ts +22 -3
- package/src/scripts/check_generator_sync.ts +71 -0
- package/src/scripts/check_host_docs_digest.ts +786 -0
- package/src/scripts/check_host_format_column.ts +559 -0
- package/src/scripts/check_input_references.ts +228 -0
- package/src/scripts/check_inputs_parity.ts +235 -0
- package/src/scripts/check_kernel_edit_ratified.ts +47 -5
- package/src/scripts/check_kill_switch_table.ts +294 -0
- package/src/scripts/check_placeholder_drift.ts +221 -0
- package/src/scripts/check_read_surface_coverage.ts +515 -0
- package/src/scripts/check_release_holds.ts +2 -1
- package/src/scripts/check_routing_coverage.ts +378 -11
- package/src/scripts/check_static_parity.ts +17 -0
- package/src/scripts/ci_settle.ts +145 -19
- package/src/scripts/closure_scan.ts +115 -1
- package/src/scripts/compile_hook_manifest.ts +60 -16
- package/src/scripts/external_sources_denylist.json +29 -4
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +42 -5
- package/src/scripts/hooks/block_config_weakening.ts +229 -4
- package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
- package/src/scripts/hooks/block_plumbing_writes.ts +314 -0
- package/src/scripts/hooks/bundle_integrity.ts +291 -0
- package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
- package/src/scripts/hooks/code_graph_context_hook.ts +1 -2
- package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/concern_timings.ts +94 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
- package/src/scripts/hooks/design_pass_hook.ts +100 -9
- package/src/scripts/hooks/design_slop_hook.ts +1 -2
- package/src/scripts/hooks/dispatch_hook.ts +25 -19
- package/src/scripts/hooks/edit_shape_hook.ts +1 -2
- package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
- package/src/scripts/hooks/evidence_independence.ts +1 -2
- package/src/scripts/hooks/exit_codes.ts +128 -0
- package/src/scripts/hooks/host_lowering.json +1 -0
- package/src/scripts/hooks/host_lowering.ts +128 -3
- package/src/scripts/hooks/host_lowering.yaml +262 -46
- package/src/scripts/hooks/host_semantics.ts +30 -2
- package/src/scripts/hooks/injection_budget.ts +9 -2
- package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
- package/src/scripts/hooks/journal_record_hook.ts +1 -2
- package/src/scripts/hooks/memory_recall_hook.ts +1 -2
- package/src/scripts/hooks/obligation_settle_hook.ts +151 -5
- package/src/scripts/hooks/one_question_per_ask_hook.ts +25 -4
- package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
- package/src/scripts/hooks/permission_gate.ts +1 -2
- package/src/scripts/hooks/push_settle_hook.ts +1 -3
- package/src/scripts/hooks/replay_hook.ts +2 -1
- package/src/scripts/hooks/reread_guard_hook.ts +1 -2
- package/src/scripts/hooks/review_baseline_hook.ts +1 -2
- package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
- package/src/scripts/hooks/rule_inject_hook.ts +8 -5
- package/src/scripts/hooks/run_continuation_hook.ts +13 -15
- package/src/scripts/hooks/skill_route_hook.ts +1 -2
- package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
- package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
- package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
- package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
- package/src/scripts/hooks/table_fingerprint.ts +49 -0
- package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
- package/src/scripts/hooks/telemetry_usage_hook.ts +1 -2
- package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
- package/src/scripts/hooks/turn_end_gate_hook.ts +337 -324
- package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
- package/src/scripts/install.ts +3 -4
- package/src/scripts/lint_deny_text.ts +419 -0
- package/src/scripts/lint_exit_codes.ts +262 -0
- package/src/scripts/lint_hook_manifest.ts +55 -0
- package/src/scripts/lint_roadmap_blockers.ts +78 -12
- package/src/scripts/lint_skill_descriptions.ts +1 -1
- package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
- package/src/scripts/mcp_server/prompts.ts +142 -1
- package/src/scripts/measure_skill_ranker_baseline.ts +305 -51
- package/src/scripts/measure_turn_end_gate.ts +193 -0
- package/src/scripts/memory_lookup.ts +1 -1
- package/src/scripts/prepack-check.mjs +53 -0
- package/src/scripts/release_publication.ts +13 -1
- package/src/scripts/report_invocation_surface.ts +383 -0
- package/src/scripts/report_obligation_carriers.ts +699 -0
- package/src/scripts/report_obligation_writer_split.ts +236 -0
- package/src/scripts/report_skill_menu_census.ts +208 -0
- package/src/scripts/report_standing_payload_by_host.ts +1 -1
- package/src/scripts/roadmap_verify_share.ts +213 -0
- package/src/scripts/schemas/command.schema.json +44 -0
- package/src/scripts/schemas/review-findings.schema.json +4 -0
- package/src/scripts/schemas/skill.schema.json +44 -0
- package/src/scripts/self_review_gate.ts +359 -10
- package/src/scripts/semantic_parity_census.ts +314 -0
- package/src/scripts/session_eol_report.ts +54 -1
- package/src/scripts/sweep_dead_scan_roots.ts +1 -1
- package/src/scripts/trigger_eval_grandfather.json +0 -2
- package/src/scripts/trigger_eval_rotation.ts +122 -27
- package/src/scripts/ui_conformance_probe.ts +156 -4
- package/src/scripts/update_prices.ts +85 -3
- package/src/scripts/write_bundle_digest.mjs +74 -0
- package/src/server/schemas/settings.ts +31 -0
- package/src/templates/consumer-settings/ONBOARDING.md +1 -0
- package/dist/ui/assets/index-DH1xc62v.js +0 -29
- package/dist/ui/assets/index-jSfMW8_t.css +0 -1
- /package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-7RQP4UDH.js} +0 -0
|
@@ -108,8 +108,8 @@
|
|
|
108
108
|
},
|
|
109
109
|
"tool_result_bytes": {
|
|
110
110
|
"definition": "road-to-inbox-harvest-2026-08-d-context-ledger 1.1: UTF-8 bytes of every tool RESULT entering context, per call, keyed by tool name. Closes the gap `unbounded_output_advisory_rate` registered above - the injection census measures what this suite puts into context, this measures what the host's tools do, and the second is the larger term. CAPTURE ONLY: the concern emits zero bytes to the model, so it carries no per-concern injection cap; it is registered here because this file already owns the owner/review discipline for context-cost instruments.",
|
|
111
|
-
"instrument": "the `tool-result-bytes` concern (src/scripts/hooks/tool_result_bytes_hook.ts) on post_tool_use, appending {ts, tool,
|
|
112
|
-
"privacy": "census lines carry a tool NAME,
|
|
111
|
+
"instrument": "the `tool-result-bytes` concern (src/scripts/hooks/tool_result_bytes_hook.ts) on post_tool_use, appending {ts, tool, raw_bytes, delivered_bytes, measurable} to agents/runtime/state/tool-result-census.jsonl, and ONLY where the maintainer workspace resolves (or AGENT_CONFIG_TOOL_BYTE_CENSUS=1) - a per-call write in every consumer install would be a second unread instrument",
|
|
112
|
+
"privacy": "census lines carry a tool NAME, two byte COUNTS and a timestamp - the record type has no field able to hold file contents, command output or any free text, so there is no scrubber that can fail",
|
|
113
113
|
"threshold": "none committed before data - baseline first, threshold at review date. A `measurable: false` line means the host's payload carried no readable result; that absence is itself a coverage finding and is never counted as zero bytes"
|
|
114
114
|
},
|
|
115
115
|
"turns_per_task": {
|
|
@@ -26,6 +26,26 @@
|
|
|
26
26
|
# that is a review judgement — so the honest claim is that the lint makes the
|
|
27
27
|
# OMISSION impossible, not that it makes the answer true. Stated here rather
|
|
28
28
|
# than discovered by someone who trusted it further than it goes.
|
|
29
|
+
#
|
|
30
|
+
# ## `unit` and `basis` — how the number was obtained, beside the number
|
|
31
|
+
#
|
|
32
|
+
# An entry MAY carry `unit:` and MUST carry `basis:` when its unit is `bytes`.
|
|
33
|
+
# `basis` is one of:
|
|
34
|
+
#
|
|
35
|
+
# measured — something counted it. A length, a sum, a row count.
|
|
36
|
+
# derived — arithmetic over a measured quantity. Never a count.
|
|
37
|
+
# proxy — a different quantity standing in for the one named.
|
|
38
|
+
# unavailable — not observable from this package. No figure is produced.
|
|
39
|
+
#
|
|
40
|
+
# The field exists because a derived byte figure and a counted one look
|
|
41
|
+
# identical in a cell, and this tree has already been burned by a number quoted
|
|
42
|
+
# onward without the conditions that produced it. `git count-objects -v` is the
|
|
43
|
+
# standing example of the confusion: it measures a LOCAL object store and is
|
|
44
|
+
# never a transfer figure, whatever the unit says.
|
|
45
|
+
#
|
|
46
|
+
# An `unavailable` entry declares a term that is out of reach, so that a total
|
|
47
|
+
# built without it is visibly incomplete rather than quietly wrong. It produces
|
|
48
|
+
# no value and must never be estimated into a sum.
|
|
29
49
|
|
|
30
50
|
metrics:
|
|
31
51
|
- id: attribution-shape-block-count
|
|
@@ -122,3 +142,97 @@ metrics:
|
|
|
122
142
|
assumption that widening helped -- which is the assumption the whole
|
|
123
143
|
roadmap exists to test rather than to make. Without it the estate grows
|
|
124
144
|
and nothing can say whether it should have.
|
|
145
|
+
|
|
146
|
+
# ── Bytes ───────────────────────────────────────────────────────────────
|
|
147
|
+
#
|
|
148
|
+
# `road-to-a-bytes-row-that-exists`. Before these, a statement of the form
|
|
149
|
+
# "this change moved N bytes" could not be made at all: the tree measured
|
|
150
|
+
# tokens well and bytes not at all, and no row here could have held one.
|
|
151
|
+
|
|
152
|
+
- id: tool-raw-bytes
|
|
153
|
+
unit: bytes
|
|
154
|
+
basis: measured
|
|
155
|
+
producer: src/scripts/hooks/tool_result_bytes_hook.ts (`raw_bytes`, per post_tool_use dispatch)
|
|
156
|
+
consumer: >-
|
|
157
|
+
the `tool_result_bytes` stream registered in `src/config/hook-token-budget.json`,
|
|
158
|
+
and the maintainer reading `agents/runtime/state/tool-result-census.jsonl`
|
|
159
|
+
before a tool-result distribution is published.
|
|
160
|
+
decision: >-
|
|
161
|
+
whether a cap on tool-result size is worth introducing, and at what
|
|
162
|
+
value. The distribution has never been observed, so the threshold stays
|
|
163
|
+
uncommitted until this counter has written one.
|
|
164
|
+
absent: >-
|
|
165
|
+
every context-cost claim this package makes is computed against a
|
|
166
|
+
denominator that excludes its own dominant term — the hook-injection
|
|
167
|
+
census measures what this suite puts into context and nothing measures
|
|
168
|
+
what the host's tools do.
|
|
169
|
+
|
|
170
|
+
- id: tool-delivered-bytes
|
|
171
|
+
unit: bytes
|
|
172
|
+
basis: measured
|
|
173
|
+
producer: src/scripts/hooks/tool_result_bytes_hook.ts (`delivered_bytes`, same record)
|
|
174
|
+
consumer: >-
|
|
175
|
+
the same census reader, comparing it against `tool-raw-bytes` on the same
|
|
176
|
+
line. Equal by construction today, because nothing in this tree rewrites
|
|
177
|
+
a tool result on its way to the model.
|
|
178
|
+
decision: >-
|
|
179
|
+
whether a result-rewriting mechanism, if one ever lands, reduced anything.
|
|
180
|
+
A non-zero gap between the two columns is the reduction; no gap is no
|
|
181
|
+
reduction.
|
|
182
|
+
absent: >-
|
|
183
|
+
a later release that starts trimming tool results has no before-figure
|
|
184
|
+
and can only assert an improvement. The two columns exist now precisely
|
|
185
|
+
so that claim is a measured delta later rather than a story.
|
|
186
|
+
|
|
187
|
+
- id: transcript-bytes-per-token
|
|
188
|
+
unit: bytes_per_token
|
|
189
|
+
basis: measured
|
|
190
|
+
producer: src/scripts/_lib/cc_transcript.ts (`measureBytesPerToken`)
|
|
191
|
+
consumer: >-
|
|
192
|
+
`cache_realization_report` and `session_eol_report`, which multiply a
|
|
193
|
+
token column by it, and any reader checking that division — the two sums
|
|
194
|
+
it was divided from travel with it on every emitted value.
|
|
195
|
+
decision: >-
|
|
196
|
+
whether a byte figure may be derived from this corpus at all. A corpus
|
|
197
|
+
that supplies no ratio produces no byte figure, rather than borrowing one
|
|
198
|
+
measured somewhere else.
|
|
199
|
+
absent: >-
|
|
200
|
+
the only remaining way to turn a token count into bytes is a constant in
|
|
201
|
+
a script, which is correct for the content that produced it and silently
|
|
202
|
+
wrong for everything else — the third conflicting figure this tree has
|
|
203
|
+
already paid to learn about twice.
|
|
204
|
+
|
|
205
|
+
- id: provider-bytes-per-call
|
|
206
|
+
unit: bytes
|
|
207
|
+
basis: derived
|
|
208
|
+
producer: >-
|
|
209
|
+
src/scripts/_lib/cc_transcript.ts (`derivedBytes`), surfaced as
|
|
210
|
+
`provider_bytes` by `cache_realization_report` and `session_eol_report`.
|
|
211
|
+
consumer: >-
|
|
212
|
+
the maintainer sizing what a round re-sends, and `check_claims`, which
|
|
213
|
+
refuses a public byte figure whose metric marker resolves to no entry here.
|
|
214
|
+
decision: >-
|
|
215
|
+
whether the volume a stateless chat round re-sends is large enough to act
|
|
216
|
+
on. Every existing reading of that cost is a token count; this is the
|
|
217
|
+
first one in the unit a network is billed in.
|
|
218
|
+
absent: >-
|
|
219
|
+
the re-send cost has no byte figure at all, so any statement about it is
|
|
220
|
+
either a token count wearing the wrong unit or an estimate with nothing
|
|
221
|
+
behind it. That is the state this metric was added to end.
|
|
222
|
+
|
|
223
|
+
- id: host-fetch-and-transport-bytes
|
|
224
|
+
unit: bytes
|
|
225
|
+
basis: unavailable
|
|
226
|
+
producer: >-
|
|
227
|
+
none — the host's own fetch tool and the model transport are not
|
|
228
|
+
observable from inside this package, and no producer is planned.
|
|
229
|
+
consumer: >-
|
|
230
|
+
any reader of a byte total, who needs to see which terms the total does
|
|
231
|
+
NOT contain. The entry is the disclosure; there is no value to read.
|
|
232
|
+
decision: >-
|
|
233
|
+
whether a byte total may be published as complete. It may not: these
|
|
234
|
+
terms are out of reach, so every total is a partial one and says so.
|
|
235
|
+
absent: >-
|
|
236
|
+
the two unreachable terms get estimated into a sum to make it look whole,
|
|
237
|
+
and the estimate becomes the figure everybody quotes. Declaring them
|
|
238
|
+
unavailable is what stops a guess from entering a total.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"owner": "maintainer",
|
|
3
|
+
"review_by": "2027-09-29",
|
|
4
|
+
"seeded_at": "2026-09-29",
|
|
5
|
+
"note": "Shrink-only ceilings on FOREIGN placeholder syntaxes in prose, per road-to-an-invocation-contract-that-reaches-the-wire 1.2. The unit is a token outside fenced blocks, indented blocks and inline code spans — defined and argued in src/scripts/report_invocation_surface.ts's module header, with the per-file breakdown in agents/evidence/analysis/invocation-surface-census.md. Seeded at what this tree measures, not at the roadmap's figures: those counted tokens inside demonstrated code (JS template literals, HCL, a table-cell marker the prose talks about) and do not reproduce. The plurality form is deliberately ungated. Raising a ceiling is a reviewable diff, and the reason belongs in it.",
|
|
6
|
+
"ceilings": {
|
|
7
|
+
"angle-upper": 3,
|
|
8
|
+
"double-brace": 0
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
},
|
|
11
11
|
"seed": {
|
|
12
12
|
"rules": 0.9623,
|
|
13
|
-
"skills": 0.
|
|
13
|
+
"skills": 0.3445
|
|
14
14
|
},
|
|
15
15
|
"seed_provenance": {
|
|
16
16
|
"rules": "102 covered of 106 routed rules on 2026-09-13; was 101 of 105 on 2026-09-08 and 94 of 105 (tier-1 21/24, tier-2 73/81) on 2026-08-25.",
|
|
17
|
-
"skills": "
|
|
18
|
-
"finding": "The gap between the two seeds IS the roadmap defect D1, quantified. The rules surface is ~90% covered by a deterministic corpus that can fail a PR; the skills surface -- the one production routes on -- is
|
|
17
|
+
"skills": "103 of 299 skills carry evals/triggers.json on 2026-10-01; was 102 on 2026-09-30, 101 on 2026-09-19 and 100 on 2026-08-30.",
|
|
18
|
+
"finding": "The gap between the two seeds IS the roadmap defect D1, quantified. The rules surface is ~90% covered by a deterministic corpus that can fail a PR; the skills surface -- the one production routes on -- is far lower and is covered only by a harness that is advisory only, never gating (rule_trigger_eval.ts:4). The figure is deliberately not restated here (corrected 2026-10-01): it read `25%` against a seed that had since moved to 0.3445, which is the same count-drift this file's own history entries keep repairing one field away. The live value is `seed.skills`, two keys above."
|
|
19
19
|
},
|
|
20
20
|
"history": [
|
|
21
21
|
{
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"scope": "rules",
|
|
52
52
|
"from": 0.9619,
|
|
53
53
|
"to": 0.9623,
|
|
54
|
-
"reason": "road-to-adversarial-verification-and-long-runs Phase 1.1 added `test-first` as a routed rule, which moved the DENOMINATOR from 105 to 106 -- the ratio-falls-two-ways case this file's own _comment names, and the one that is a defect to record rather than repair. The repair is the corpus file, not the seed: `tests/eval/routing-matrix/test-first.yaml` landed in the same change (101 -> 102 of 106), so the ratio ROSE across a denominator increase rather than merely holding. The rule is trigger-less by design, so its positives describe the situations it governs rather than strings that summon it, and its near-misses are its own
|
|
54
|
+
"reason": "road-to-adversarial-verification-and-long-runs Phase 1.1 added `test-first` as a routed rule, which moved the DENOMINATOR from 105 to 106 -- the ratio-falls-two-ways case this file's own _comment names, and the one that is a defect to record rather than repair. The repair is the corpus file, not the seed: `tests/eval/routing-matrix/test-first.yaml` landed in the same change (101 -> 102 of 106), so the ratio ROSE across a denominator increase rather than merely holding. The rule is trigger-less by design, so its positives describe the situations it governs rather than strings that summon it, and its near-misses are its own § Does NOT fire set. The seed follows the gain because this file's test asserts seed == live measurement in both directions; it may never be LOWERED."
|
|
55
55
|
},
|
|
56
56
|
{
|
|
57
57
|
"date": "2026-09-19",
|
|
@@ -59,6 +59,20 @@
|
|
|
59
59
|
"from": 0.3344,
|
|
60
60
|
"to": 0.3378,
|
|
61
61
|
"reason": "road-to-release-holds-that-refuse step 1.4 authored one corpus file for `roadmap-writing` (100 -> 101 of 299) and removed its now-false entry from trigger_eval_grandfather.json. ONE file rather than a wave: the step asked for a single positive case whose expected output is a re-sequenced phase rather than a hold, and the eval set is where that expectation is pinned. The seed rises so the gain cannot be given back silently; it may never be LOWERED. Two published measurements moved with it and were re-taken in the same change -- routing-body-signal-verdict.json and the measure_delivery_sets record -- because the trigger corpus is their input, so any skill gaining an eval set re-takes both."
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"date": "2026-09-30",
|
|
65
|
+
"scope": "skills",
|
|
66
|
+
"from": 0.3378,
|
|
67
|
+
"to": 0.3411,
|
|
68
|
+
"reason": "road-to-a-component-taxonomy-we-follow-but-never-force authored one corpus file for `ui-component-architect` (101 -> 102 of 299). Not a sought gain: the branch edits that skill's placement section, and check_routing_coverage requires a corpus for every skill a diff touches, so the file is the gate's own remedy rather than a coverage push. Five should-trigger cases, one per shape the skill decides, and five near misses whose vocabulary overlaps hardest -- styling an existing component, inventorying what the project owns (existing-ui-audit, which runs first and feeds this one), a backend composition question, a prop rename, and general React knowledge with no component to shape. The seed rises so the gain cannot be given back silently; it may never be LOWERED. The two published measurements the 2026-09-19 entry names -- routing-body-signal-verdict.json and the measure_delivery_sets record -- are re-taken in the same change, for the reason that entry gives: the trigger corpus is their input."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"date": "2026-10-01",
|
|
72
|
+
"scope": "skills",
|
|
73
|
+
"from": 0.3411,
|
|
74
|
+
"to": 0.3445,
|
|
75
|
+
"reason": "road-to-corpus-refresh-cadence-shape step 1.2a authored one corpus file for `accessibility-auditor` (102 -> 103 of 299). Same shape as the 2026-09-30 entry and for the same reason: that step re-checked the skill's grounding corpus against WCAG 2.2 and the APG, which edited src/skills/accessibility-auditor/data/, and check_routing_coverage requires a corpus for every skill a diff touches. Ten cases -- five exemplars including the German phrasing the skill's own description names, three near-misses -- tailwind-engineer for token-level contrast and ui-component-architect for landmarks and heading order, both named in that skill's SKILL.md, plus existing-ui-audit for component inventory, which it does not name and which is included because it runs first over the same surface -- and two counterexamples, one guarding the 'accessible' homonym. The seed rises so the gain cannot be given back silently; it may never be LOWERED. The trigger-corpus holdout pin is re-taken in the same change -- and this growth moves it for the first time on the HOLDOUT side (18 -> 19), because sha256('accessibility-auditor')[0:2] is 0x2f = 47, below the ceiling of 51. That is recorded in the holdout artefact with its ordering claim explicitly scoped to the 18 originally sealed rows. The two published measurements the 2026-09-19 and 2026-09-30 entries name -- routing-body-signal-verdict.json and the measure_delivery_sets record -- are deliberately NOT re-taken here, and the departure from those two entries is stated rather than left to be noticed. The reason is the partition, not the date: this growth is HOLDOUT-side, so the train input both measurements read is byte-identical to what it was before this change -- all 84 train rows reproduce -- and re-taking them would publish the same measurement under a newer date, which is a freshness claim nothing earned. The two artefacts are NOT in the same state, and conflating them would repeat the error this sentence replaces: routing-body-signal-verdict.json carries `corpus.partition: train` and `train_corpora: 84` and is current for the train partition; delivery-set-result-2026-08-31.md still reads `82 train corpora, 764 distinct prompts` and carries no re-take section for either the 2026-09-19 or the 2026-09-30 growth, so it is two corpora stale and those two entries' claim that it `is re-taken in the same change` is not backed by the tree. Repairing it is a train-side measurement re-run, out of scope for a holdout-side growth, and is named here so the gap is attributed rather than inherited silently. A FUTURE train-side growth owes both re-takes. Separately, TWO entries were removed from trigger_eval_grandfather.json, and the two reds are of different kinds. `ui-component-architect` was red ON MAIN: it gained a corpus on 2026-09-30 and its now-false allowlist entry was not removed then, which is the convention the 2026-09-19 entry sets and the 2026-09-30 entry omitted. `accessibility-auditor` was NOT red on main -- on main it ships no corpus at all, and check_trigger_eval_presence fires only on an allowlist entry whose skill HAS one, so its entry went stale the moment this change added the corpus. That red is created here and removed here, in the same change. Stated separately because a reader auditing why two entries were dropped would otherwise be told one pre-existing condition explains both."
|
|
62
76
|
}
|
|
63
77
|
]
|
|
64
78
|
}
|
|
@@ -114,7 +114,8 @@ import {
|
|
|
114
114
|
resolve_project_root,
|
|
115
115
|
type TraceRecord,
|
|
116
116
|
} from '../_lib/agent_settings.js';
|
|
117
|
-
import { executionJson,
|
|
117
|
+
import { executionJson, forgeProtectionJsonFor } from './doctor_execution.js';
|
|
118
|
+
import { checkOfflineReadiness, trafficEnvironmentJson } from './doctor_network_posture.js';
|
|
118
119
|
import * as ai_council_clients from '../ai_council/clients.js';
|
|
119
120
|
import * as ai_council_config from '../ai_council/config.js';
|
|
120
121
|
import {
|
|
@@ -1450,23 +1451,7 @@ function _check_mcp_mode(project_root: string): Dict {
|
|
|
1450
1451
|
};
|
|
1451
1452
|
}
|
|
1452
1453
|
|
|
1453
|
-
|
|
1454
|
-
const script = path.join(_package_root(), 'src', 'scripts', 'hermetic-install.sh');
|
|
1455
|
-
if (!pathExists(script)) {
|
|
1456
|
-
return {
|
|
1457
|
-
id: 'offline-readiness',
|
|
1458
|
-
status: 'warn',
|
|
1459
|
-
message: 'src/scripts/hermetic-install.sh not found in package',
|
|
1460
|
-
remedy: 'reinstall @event4u/agent-config or pull missing files',
|
|
1461
|
-
};
|
|
1462
|
-
}
|
|
1463
|
-
return {
|
|
1464
|
-
id: 'offline-readiness',
|
|
1465
|
-
status: 'ok',
|
|
1466
|
-
message: 'verified-offline install entrypoint present',
|
|
1467
|
-
remedy: '',
|
|
1468
|
-
};
|
|
1469
|
-
}
|
|
1454
|
+
const _check_offline_readiness = (): Dict => checkOfflineReadiness(_package_root());
|
|
1470
1455
|
|
|
1471
1456
|
function _check_stale_orphans(): Dict {
|
|
1472
1457
|
const gdi = global_deploy_inventory;
|
|
@@ -3077,7 +3062,8 @@ function _emit_json(
|
|
|
3077
3062
|
};
|
|
3078
3063
|
if (origin !== null) payload['project_root_origin'] = origin;
|
|
3079
3064
|
payload['execution'] = executionJson(() => iter_setting_overrides({ cwd: project_root }));
|
|
3080
|
-
payload['forge_protection'] =
|
|
3065
|
+
payload['forge_protection'] = forgeProtectionJsonFor(String(project_root)); // 3.2 · AC-5
|
|
3066
|
+
payload['traffic_environment'] = trafficEnvironmentJson(process.env);
|
|
3081
3067
|
if (checks !== null) {
|
|
3082
3068
|
payload['checks'] = checks;
|
|
3083
3069
|
const drift = missing.length + modified.length + foreign.length + tag_drift.length;
|
|
@@ -23,6 +23,15 @@ import {
|
|
|
23
23
|
protectionActions,
|
|
24
24
|
type ForgeReading,
|
|
25
25
|
} from '../_lib/forge_protection.js';
|
|
26
|
+
import {
|
|
27
|
+
FORGE_TOTAL_BUDGET_MS,
|
|
28
|
+
budgetOf,
|
|
29
|
+
forgeReadingFor,
|
|
30
|
+
liveForgeApi,
|
|
31
|
+
originUrl,
|
|
32
|
+
resolveForgeRepo,
|
|
33
|
+
type Runner,
|
|
34
|
+
} from '../_lib/forge_reader.js';
|
|
26
35
|
|
|
27
36
|
type Dict = Record<string, unknown>;
|
|
28
37
|
|
|
@@ -52,12 +61,17 @@ export function executionJson(overrides: () => OverrideStream): Dict {
|
|
|
52
61
|
/**
|
|
53
62
|
* The `forge_protection` block of `doctor --json` — Phase 3.2.
|
|
54
63
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
64
|
+
* **THIS function does not reach the network; `doctor` now does.** The mapper
|
|
65
|
+
* stays pure and takes an INJECTED reading — a caller that queried the forge
|
|
66
|
+
* passes it, one that did not passes the all-null reading, which produces five
|
|
67
|
+
* `unread` rows that still name the call each value would come from. What
|
|
68
|
+
* changed is the caller: `forgeProtectionJsonFor` below acquires that reading,
|
|
69
|
+
* so `doctor --json` does make `gh` calls. Phase 3.2's "`doctor` does not reach
|
|
70
|
+
* the network" is SUPERSEDED, and this header said otherwise for three review
|
|
71
|
+
* rounds while the function forty lines down called it a reversal — the third
|
|
72
|
+
* copy of a refuted argument in this diff, each found by a different round.
|
|
73
|
+
* Corrected here for the reason the others were: a header is where a dead
|
|
74
|
+
* argument gets reused.
|
|
61
75
|
*
|
|
62
76
|
* That is the honest shape for *read, never guessed*: the block is always
|
|
63
77
|
* present, a value appears only when a named call produced it, and `unread` is a
|
|
@@ -67,24 +81,93 @@ export function executionJson(overrides: () => OverrideStream): Dict {
|
|
|
67
81
|
* `actions` carries the human ACTION lines — the step's own words are that a
|
|
68
82
|
* missing row is a blocker entry and NOT a halt, so this reports and returns.
|
|
69
83
|
*/
|
|
70
|
-
export function forgeProtectionJson(reading: ForgeReading): Dict {
|
|
84
|
+
export function forgeProtectionJson(reading: ForgeReading, repo: string | null = null): Dict {
|
|
71
85
|
const rows = forgeProtectionRows(reading);
|
|
86
|
+
const readFromForge = rows.some((r) => r.state !== 'unread');
|
|
87
|
+
// **The slug is reported only when something was actually read, and that
|
|
88
|
+
// invariant is structural rather than a caller's promise.** The first
|
|
89
|
+
// version reported whatever the git remote resolved to, which is a LOCAL
|
|
90
|
+
// read that succeeds with no network — so an ordinary offline run (GitHub
|
|
91
|
+
// remote present, no connectivity, no opt-out) emitted a named repository
|
|
92
|
+
// and substituted sources while every row was `unread`. That made the
|
|
93
|
+
// claim this change rests on — offline output is what Phase 3.2 shipped —
|
|
94
|
+
// false on exactly the failed-live path the council condition names. The
|
|
95
|
+
// earlier verification only ever exercised the opt-out switch and read it
|
|
96
|
+
// as offline; the two are not the same path.
|
|
97
|
+
const anonymous = repo === null || !readFromForge;
|
|
98
|
+
const name = (s: string): string =>
|
|
99
|
+
anonymous ? s : s.replace(/\{owner\}\/\{repo\}/g, () => repo);
|
|
72
100
|
return {
|
|
101
|
+
repository: readFromForge ? repo : null,
|
|
73
102
|
rows: rows.map((r) => ({
|
|
74
103
|
id: r.id,
|
|
75
104
|
state: r.state,
|
|
76
|
-
source: r.source,
|
|
105
|
+
source: name(r.source),
|
|
77
106
|
detail: r.detail,
|
|
78
107
|
})),
|
|
79
|
-
actions: protectionActions(rows),
|
|
80
|
-
read_from_forge:
|
|
108
|
+
actions: protectionActions(rows).map(name),
|
|
109
|
+
read_from_forge: readFromForge,
|
|
81
110
|
};
|
|
82
111
|
}
|
|
83
112
|
|
|
84
|
-
/**
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
113
|
+
/**
|
|
114
|
+
* The block as `doctor` itself builds it — AC-5.
|
|
115
|
+
*
|
|
116
|
+
* Phase 3.2 left `forgeProtectionJson` with no production caller, so the
|
|
117
|
+
* command the criterion names reported five `unread` rows while the forge
|
|
118
|
+
* satisfied all five, and the rows were only ever established by a human
|
|
119
|
+
* running `gh api` and reading the mapper in a test. This is the wiring that
|
|
120
|
+
* makes the named command answer for itself.
|
|
121
|
+
*
|
|
122
|
+
* **This IS a reversal of Phase 3.2's module boundary, and the roadmap records
|
|
123
|
+
* the supersession.** An earlier draft of this comment argued the opposite —
|
|
124
|
+
* "an addition rather than a reversal" — which a 2/2 convergent council pass
|
|
125
|
+
* refuted: that note recorded two boundaries, `doctor` does not reach the
|
|
126
|
+
* network AND the reading is injected, and this reverses both. The refuted
|
|
127
|
+
* argument is corrected here rather than left standing, because a comment is
|
|
128
|
+
* what the next reader of this module finds first and is where a refuted
|
|
129
|
+
* reasoning would get reused.
|
|
130
|
+
*
|
|
131
|
+
* What survives is the AVAILABILITY property, which is narrower than the
|
|
132
|
+
* earlier claim and is the one the supersession rests on: every failure path in
|
|
133
|
+
* `forgeReadingFor` — opt-out switch, no GitHub remote, no `gh`, no
|
|
134
|
+
* credentials, timeout, unparseable payload, a throw — returns the same
|
|
135
|
+
* `UNREAD_FORGE` reading, so offline the ROWS, their `source` templates and the
|
|
136
|
+
* action lines are what Phase 3.2 printed. Not the whole document: the block
|
|
137
|
+
* gained a top-level `repository` key, which is `null` on every such path. A
|
|
138
|
+
* consumer validating `forge_protection` against a closed schema sees that one
|
|
139
|
+
* added key; "prints exactly what it printed before" was too strong and is not
|
|
140
|
+
* claimed.
|
|
141
|
+
*
|
|
142
|
+
* `process.env` and the git remote are read HERE rather than inside the reader,
|
|
143
|
+
* so the reader stays injectable and every branch above is reachable from a
|
|
144
|
+
* test without spawning anything.
|
|
145
|
+
*/
|
|
146
|
+
export interface ForgeDeps {
|
|
147
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
148
|
+
/** The subprocess runner for both `git` and `gh`. */
|
|
149
|
+
readonly run?: Runner;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function forgeProtectionJsonFor(root: string, deps: ForgeDeps = {}): Dict {
|
|
153
|
+
// The composition root — budget, thunk, host and cwd wired together — had
|
|
154
|
+
// no seam and therefore no test, so the opt-out-before-spawn and host
|
|
155
|
+
// binding were asserted one layer down against fakes while a regression in
|
|
156
|
+
// the real wiring stayed invisible. `deps` is that seam; production passes
|
|
157
|
+
// nothing and gets `process.env` plus the real spawn.
|
|
158
|
+
const env = deps.env ?? process.env;
|
|
159
|
+
const run = deps.run;
|
|
160
|
+
const remaining = budgetOf(FORGE_TOTAL_BUDGET_MS);
|
|
161
|
+
const read = forgeReadingFor({
|
|
162
|
+
env,
|
|
163
|
+
// A thunk, so the opt-out short-circuits before `git` spawns.
|
|
164
|
+
resolveRepo: () => resolveForgeRepo(originUrl(root, run)),
|
|
165
|
+
// Built from the RESOLVED target, so `gh` is addressed at the host and
|
|
166
|
+
// directory the slug came from. The per-call ceiling is whatever is
|
|
167
|
+
// left of the whole-read budget, so a call admitted near the deadline
|
|
168
|
+
// cannot run past it.
|
|
169
|
+
apiFor: (t) => liveForgeApi(remaining, run, { host: t.host, cwd: root }),
|
|
170
|
+
remaining,
|
|
171
|
+
});
|
|
172
|
+
return forgeProtectionJson(read.reading, read.repo);
|
|
173
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What this install does on the network — the two `doctor` surfaces that
|
|
3
|
+
* answer that question, in one module under the source-size cap.
|
|
4
|
+
*
|
|
5
|
+
* `road-to-host-traffic-knobs-that-ship` Phases 2.1 and 2.2. Its own module
|
|
6
|
+
* rather than a function inside `cmd_doctor.ts` for the reason
|
|
7
|
+
* `_lib/doctor_runtime_checks.ts` states for itself: that file is ~3,600 lines
|
|
8
|
+
* and `check_source_size_budget` counts every line above 1,500, so a change
|
|
9
|
+
* that adds lines there pays for them by taking lines away. Wiring the traffic
|
|
10
|
+
* section in costs two lines; `checkOfflineReadiness` moving here returns more
|
|
11
|
+
* than that, and lands beside a cohesive sibling rather than in an arbitrary
|
|
12
|
+
* bin — "can this install work with no registry" and "which host traffic is
|
|
13
|
+
* switched off" are the same question asked from two ends.
|
|
14
|
+
*
|
|
15
|
+
* `checkOfflineReadiness` is a PURE MOVE: same id, same statuses, same message
|
|
16
|
+
* and remedy strings. Its `pathExists` is `fs.statSync` in a try, matching the
|
|
17
|
+
* helper it used in `cmd_doctor.ts` — `existsSync` would differ on a path whose
|
|
18
|
+
* parent denies traversal, and this is a move, not a repair.
|
|
19
|
+
*
|
|
20
|
+
* Why the traffic section reports rather than decides: the variables below belong to the HOST, not to this package. Writing one into
|
|
21
|
+
* a consumer environment changes behaviour the consumer never asked to change,
|
|
22
|
+
* and — per the mapping measured in `docs/setup/host-traffic-environment.md` —
|
|
23
|
+
* could stop their background security updates as a side effect of a telemetry
|
|
24
|
+
* preference. So this module reads `env` and returns; it writes nothing, opens
|
|
25
|
+
* no socket, and touches no settings file. Two consecutive calls return equal
|
|
26
|
+
* payloads and leave the tree byte-identical.
|
|
27
|
+
*
|
|
28
|
+
* Why an inapplicable row is PRESENT and says so: the obvious implementation lists only the variables that resolve on the
|
|
29
|
+
* current host, which produces a short, clean section on a host that documents
|
|
30
|
+
* none of them. An operator reading that concludes their traffic is governed,
|
|
31
|
+
* when in fact the package simply had nothing to say. An omission that reads as
|
|
32
|
+
* a clean bill of health is the precise failure this surface exists to prevent,
|
|
33
|
+
* so a row that does not apply is emitted with the literal state
|
|
34
|
+
* {@link NOT_APPLICABLE} instead of being dropped.
|
|
35
|
+
*
|
|
36
|
+
* A variable that is SET on a host that does not document it still reports its
|
|
37
|
+
* value beside that state. The state says "this host is not known to read it";
|
|
38
|
+
* dropping the value as well would hide a fact the reader has.
|
|
39
|
+
*/
|
|
40
|
+
import * as fs from 'node:fs';
|
|
41
|
+
import * as path from 'node:path';
|
|
42
|
+
|
|
43
|
+
/** The doctor's structured check row. Mirrors `cmd_doctor`'s internal `Dict`. */
|
|
44
|
+
export interface NetworkCheck {
|
|
45
|
+
id: string;
|
|
46
|
+
status: 'ok' | 'warn' | 'fail' | 'skipped';
|
|
47
|
+
message: string;
|
|
48
|
+
remedy: string;
|
|
49
|
+
/** Structural compatibility with `cmd_doctor`'s `Dict` row type. */
|
|
50
|
+
[k: string]: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The literal a row carries where the host has no documented meaning for it. */
|
|
54
|
+
export const NOT_APPLICABLE = 'not applicable on this host';
|
|
55
|
+
|
|
56
|
+
/** The documentation surface every row points at. One copy of the mapping. */
|
|
57
|
+
export const TRAFFIC_DOC = 'docs/setup/host-traffic-environment.md';
|
|
58
|
+
|
|
59
|
+
/** Host id used when nothing in the environment identifies the host. */
|
|
60
|
+
export const UNKNOWN_HOST = 'unknown';
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* One documented host traffic variable.
|
|
64
|
+
*
|
|
65
|
+
* `hosts` lists the host ids whose shipped build was actually READ for this
|
|
66
|
+
* variable — never a host somebody expects to behave the same way. A host
|
|
67
|
+
* absent from the list gets {@link NOT_APPLICABLE}, which is the honest answer
|
|
68
|
+
* and also the safe one.
|
|
69
|
+
*/
|
|
70
|
+
export interface TrafficVariable {
|
|
71
|
+
readonly variable: string;
|
|
72
|
+
readonly hosts: readonly string[];
|
|
73
|
+
/** Host build and date the row was read from, e.g. `claude-code 2.1.284 · 2026-09-29`. */
|
|
74
|
+
readonly checked_against: string;
|
|
75
|
+
/**
|
|
76
|
+
* May THIS PACKAGE write the variable into the host env block?
|
|
77
|
+
*
|
|
78
|
+
* `false` on both traffic variables and it is not a policy setting — it is a
|
|
79
|
+
* structural fact: `install.ts`'s `WRITABLE_HOST_ENV` is a two-row allow
|
|
80
|
+
* table and the settings schema admits only those two keys, so neither
|
|
81
|
+
* traffic variable is expressible anywhere in the write path. Reported
|
|
82
|
+
* because a consumer reading `set` on a row deserves to know whether this
|
|
83
|
+
* package could have been the one that set it.
|
|
84
|
+
*/
|
|
85
|
+
readonly writable_by_this_package: boolean;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The host build every row below was read from. One place to bump on a re-check. */
|
|
89
|
+
const CHECKED = 'claude-code 2.1.284 · 2026-09-29';
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The documented set, in the order `docs/setup/host-traffic-environment.md`
|
|
93
|
+
* lists it. What each one governs — and does not — lives in that page and is
|
|
94
|
+
* deliberately not duplicated here: a second copy is a second thing to keep
|
|
95
|
+
* true, and this one would be the copy nobody re-reads.
|
|
96
|
+
*/
|
|
97
|
+
export const TRAFFIC_VARIABLES: readonly TrafficVariable[] = [
|
|
98
|
+
// Both traffic variables reach the host's update-disabled resolver, the
|
|
99
|
+
// blanket one as its third rung, so neither is writable by this package —
|
|
100
|
+
// owner-decided 2026-09-30.
|
|
101
|
+
{ variable: 'CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC', hosts: ['claude-code'], checked_against: CHECKED, writable_by_this_package: false },
|
|
102
|
+
{ variable: 'DISABLE_AUTOUPDATER', hosts: ['claude-code'], checked_against: CHECKED, writable_by_this_package: false },
|
|
103
|
+
// Size caps. Neither reaches the updater resolver, and neither suppresses a
|
|
104
|
+
// call, an update check or a download.
|
|
105
|
+
{ variable: 'BASH_MAX_OUTPUT_LENGTH', hosts: ['claude-code'], checked_against: CHECKED, writable_by_this_package: true },
|
|
106
|
+
{ variable: 'MAX_MCP_OUTPUT_TOKENS', hosts: ['claude-code'], checked_against: CHECKED, writable_by_this_package: true },
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
/** An environment map, in the shape `process.env` has. */
|
|
110
|
+
export type EnvMap = Readonly<Record<string, string | undefined>>;
|
|
111
|
+
|
|
112
|
+
/** Which host this process is running under, and whether that was observed. */
|
|
113
|
+
export interface TrafficHost {
|
|
114
|
+
host: string;
|
|
115
|
+
observed: boolean;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Resolve the host from the environment it exported.
|
|
120
|
+
*
|
|
121
|
+
* `observed` carries the same distinction `routing:doctor` draws for its own
|
|
122
|
+
* platform field: a host that identified itself is a fact, and the absence of
|
|
123
|
+
* any marker is NOT evidence of a different host — only evidence that nobody
|
|
124
|
+
* said. An unidentified host resolves to {@link UNKNOWN_HOST}, which documents
|
|
125
|
+
* no variable, so every row reads {@link NOT_APPLICABLE} rather than reporting
|
|
126
|
+
* against a host this code guessed at.
|
|
127
|
+
*/
|
|
128
|
+
export function resolveTrafficHost(env: EnvMap): TrafficHost {
|
|
129
|
+
const claude =
|
|
130
|
+
(env['CLAUDECODE'] ?? '').trim() !== '' || (env['CLAUDE_CODE_SESSION_ID'] ?? '').trim() !== '';
|
|
131
|
+
if (claude) return { host: 'claude-code', observed: true };
|
|
132
|
+
return { host: UNKNOWN_HOST, observed: false };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** One emitted row: the variable, its state on this host, and its value if set. */
|
|
136
|
+
export interface TrafficRow {
|
|
137
|
+
variable: string;
|
|
138
|
+
state: string;
|
|
139
|
+
value: string | null;
|
|
140
|
+
checked_against: string;
|
|
141
|
+
writable_by_this_package: boolean;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Build one row. A variable the host does not document reports
|
|
146
|
+
* {@link NOT_APPLICABLE} whether or not it happens to be exported.
|
|
147
|
+
*/
|
|
148
|
+
function trafficRow(spec: TrafficVariable, env: EnvMap, host: string): TrafficRow {
|
|
149
|
+
const raw = env[spec.variable];
|
|
150
|
+
const present = raw !== undefined && raw !== '';
|
|
151
|
+
const applicable = spec.hosts.includes(host);
|
|
152
|
+
return {
|
|
153
|
+
variable: spec.variable,
|
|
154
|
+
state: applicable ? (present ? 'set' : 'unset') : NOT_APPLICABLE,
|
|
155
|
+
value: present ? String(raw) : null,
|
|
156
|
+
checked_against: spec.checked_against,
|
|
157
|
+
writable_by_this_package: spec.writable_by_this_package,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The `traffic_environment` block of `agent-config doctor --json`.
|
|
163
|
+
*
|
|
164
|
+
* Read-only by construction: the only input is the injected `env` map, and the
|
|
165
|
+
* only output is a fresh object. `hostOverride` is the test seam Phase 2.2's
|
|
166
|
+
* non-Claude fixture needs, and is never passed by the production call site.
|
|
167
|
+
*
|
|
168
|
+
* `reporting_read_only` replaced a flat `read_only: true` on 2026-09-30, and the
|
|
169
|
+
* rename is the point rather than cosmetics: this REPORT is still read-only, but
|
|
170
|
+
* the PACKAGE is no longer, so the old key had become a true statement about the
|
|
171
|
+
* wrong subject. Two of the four rows are now writable by the installer when the
|
|
172
|
+
* consumer sets a cap, and `writable_by_this_package` says which — a consumer
|
|
173
|
+
* reading `set` deserves to know whether we could have been the one who set it.
|
|
174
|
+
*/
|
|
175
|
+
export function trafficEnvironmentJson(
|
|
176
|
+
env: EnvMap,
|
|
177
|
+
hostOverride?: TrafficHost,
|
|
178
|
+
): Record<string, unknown> {
|
|
179
|
+
const resolved = hostOverride ?? resolveTrafficHost(env);
|
|
180
|
+
return {
|
|
181
|
+
host: resolved.host,
|
|
182
|
+
host_observed: resolved.observed,
|
|
183
|
+
doc: TRAFFIC_DOC,
|
|
184
|
+
reporting_read_only: true,
|
|
185
|
+
writable_by_this_package: TRAFFIC_VARIABLES.filter((v) => v.writable_by_this_package).map(
|
|
186
|
+
(v) => v.variable,
|
|
187
|
+
),
|
|
188
|
+
rows: TRAFFIC_VARIABLES.map((spec) => trafficRow(spec, env, resolved.host)),
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** `fs.statSync` in a try — the `cmd_doctor.ts` helper this move preserves. */
|
|
193
|
+
function pathExists(p: string): boolean {
|
|
194
|
+
try {
|
|
195
|
+
fs.statSync(p);
|
|
196
|
+
return true;
|
|
197
|
+
} catch {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* The `offline-readiness` check — a pure move out of `cmd_doctor.ts`.
|
|
204
|
+
*
|
|
205
|
+
* `packageRoot` is passed IN rather than resolved here for the reason
|
|
206
|
+
* `doctor_runtime_checks.ts` gives for its `which` parameter: `_package_root`
|
|
207
|
+
* lives in `cmd_doctor.ts` and importing it back would make the two modules
|
|
208
|
+
* circular.
|
|
209
|
+
*/
|
|
210
|
+
export function checkOfflineReadiness(packageRoot: string): NetworkCheck {
|
|
211
|
+
const script = path.join(packageRoot, 'src', 'scripts', 'hermetic-install.sh');
|
|
212
|
+
if (!pathExists(script)) {
|
|
213
|
+
return {
|
|
214
|
+
id: 'offline-readiness',
|
|
215
|
+
status: 'warn',
|
|
216
|
+
message: 'src/scripts/hermetic-install.sh not found in package',
|
|
217
|
+
remedy: 'reinstall @event4u/agent-config or pull missing files',
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
id: 'offline-readiness',
|
|
222
|
+
status: 'ok',
|
|
223
|
+
message: 'verified-offline install entrypoint present',
|
|
224
|
+
remedy: '',
|
|
225
|
+
};
|
|
226
|
+
}
|