@event4u/agent-config 14.5.0 → 14.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +384 -3
- package/README.md +1 -1
- package/dist/agent-src/commands/analyze/decision.md +7 -0
- package/dist/agent-src/commands/analyze/inbox.md +264 -10
- package/dist/agent-src/commands/analyze.md +1 -1
- package/dist/agent-src/commands/optimize/project.md +10 -0
- package/dist/agent-src/commands/roadmap/process-full.md +132 -4
- package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +31 -0
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +161 -1
- package/dist/agent-src/rules/context-hygiene.md +2 -0
- package/dist/agent-src/rules/decision-revisit-gate.md +111 -7
- package/dist/agent-src/rules/fix-what-you-see.md +108 -0
- package/dist/agent-src/rules/no-roadmap-references.md +0 -5
- package/dist/agent-src/rules/recurring-criticism.md +110 -0
- package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
- package/dist/agent-src/rules/rule-type-governance.md +0 -2
- package/dist/agent-src/rules/self-repair-loop.md +17 -0
- package/dist/agent-src/rules/skill-quality.md +0 -2
- package/dist/agent-src/rules/source-confidentiality.md +37 -7
- package/dist/agent-src/scripts/archival_sweep.ts +117 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +19 -3
- package/dist/agent-src/scripts/gate_budget.ts +282 -0
- package/dist/agent-src/scripts/gate_execute.ts +72 -31
- package/dist/agent-src/scripts/roadmap_gates.ts +74 -3
- package/dist/agent-src/scripts/update_roadmap_progress.ts +251 -7
- package/dist/agent-src/skills/adr-create/SKILL.md +25 -3
- package/dist/agent-src/skills/ai-council/SKILL.md +48 -807
- package/dist/agent-src/skills/ai-council/references/advanced-modes.md +320 -0
- package/dist/agent-src/skills/ai-council/references/cost-and-redaction.md +99 -0
- package/dist/agent-src/skills/ai-council/references/execution-modes.md +162 -0
- package/dist/agent-src/skills/ai-council/references/output-and-synthesis.md +125 -0
- package/dist/agent-src/skills/ai-council/references/procedure.md +143 -0
- package/dist/agent-src/skills/decision-review/SKILL.md +18 -0
- package/dist/agent-src/skills/quality-tools/SKILL.md +34 -326
- package/dist/agent-src/skills/quality-tools/references/js-ts-tools.md +113 -0
- package/dist/agent-src/skills/quality-tools/references/php-tools.md +222 -0
- package/dist/agent-src/skills/roadmap-management/SKILL.md +15 -409
- package/dist/agent-src/skills/roadmap-management/references/archival.md +226 -0
- package/dist/agent-src/skills/roadmap-management/references/authoring.md +107 -0
- package/dist/agent-src/skills/roadmap-management/references/dashboard.md +47 -0
- package/dist/agent-src/skills/roadmap-management/references/lifecycle.md +58 -0
- package/dist/agent-src/skills/skill-writing/SKILL.md +34 -570
- package/dist/agent-src/skills/skill-writing/references/procedure.md +275 -0
- package/dist/agent-src/skills/skill-writing/references/read-only-scripts.md +18 -0
- package/dist/agent-src/skills/skill-writing/references/section-patterns.md +298 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +45 -2
- package/dist/agent-src/templates/scripts/telemetry/flush_sender.mjs +163 -0
- package/dist/agent-src/templates/scripts/telemetry/remote.ts +160 -7
- package/dist/agent-src/templates/scripts/telemetry/transport.ts +169 -0
- package/dist/cli/registry.js +1 -1
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/chunk-2GNWGMAL.js +172 -0
- package/dist/cli-delegate/{chunk-RLN73PR5.js → chunk-4VR4ORQ6.js} +10 -7
- package/dist/cli-delegate/chunk-6EOCNQAM.js +402 -0
- package/dist/cli-delegate/{chunk-XURJ4AQC.js → chunk-74E3TKWV.js} +381 -280
- package/dist/cli-delegate/{chunk-3UCJWMMP.js → chunk-7F3VYSM2.js} +4 -4
- package/dist/cli-delegate/{chunk-BKO2AZ7Z.js → chunk-B2ERU2VA.js} +1 -1
- package/dist/cli-delegate/{chunk-NXFWFAYV.js → chunk-G23AT6GD.js} +1 -0
- package/dist/cli-delegate/{chunk-63WSP6YZ.js → chunk-GJ5BVHXM.js} +72 -165
- package/dist/cli-delegate/{chunk-ZPSXJTLI.js → chunk-JZ4NR2TW.js} +353 -1
- package/dist/cli-delegate/chunk-MWRD5YOH.js +100 -0
- package/dist/cli-delegate/{chunk-GRRPEPRN.js → chunk-T7L2QK3K.js} +24 -5
- package/dist/cli-delegate/{chunk-EVTY7ISR.js → chunk-YM4GZDJG.js} +9 -1
- package/dist/cli-delegate/cmd_conformance.js +459 -709
- package/dist/cli-delegate/cmd_doctor.js +6 -6
- package/dist/cli-delegate/cmd_explain.js +1 -1
- package/dist/cli-delegate/cmd_export.js +7 -6
- package/dist/cli-delegate/cmd_handoff.js +6 -6
- package/dist/cli-delegate/cmd_packs_active.js +1 -1
- package/dist/cli-delegate/cmd_preflight.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +7 -6
- package/dist/cli-delegate/cmd_session_recycle.js +840 -30
- package/dist/cli-delegate/cmd_settings_migrate.js +7 -6
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +7 -6
- package/dist/cli-delegate/cmd_uninstall.js +9 -7
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +7 -6
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +72 -29
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -4
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +10 -8
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +8 -6
- package/dist/hooks/dispatch.js +3127 -2022
- package/dist/install/claudePathsPlan.js +187 -0
- package/dist/install/claudePathsPlan.js.map +1 -0
- package/dist/install/hostLayerFingerprint.js +136 -0
- package/dist/install/hostLayerFingerprint.js.map +1 -0
- package/dist/install/install.mjs +553 -425
- package/dist/install/partitionEligibility.js +276 -0
- package/dist/install/partitionEligibility.js.map +1 -0
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1744 -1191
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_src.js +41 -0
- package/dist/scripts/_lib/agent_src.js.map +1 -1
- package/dist/scripts/_lib/install_layout.js +83 -0
- package/dist/scripts/_lib/install_layout.js.map +1 -0
- package/dist/scripts/_lib/installed_lock.js +444 -0
- package/dist/scripts/_lib/installed_lock.js.map +1 -0
- package/dist/scripts/condense.js +13 -164
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/schemas/settings.js +4 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/contracts/settings-classes.md +12 -6
- package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +8 -3
- package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +26 -0
- package/docs/guidelines/agent-infra/engineering-memory-data-format.md +49 -0
- package/docs/guidelines/agent-infra/memory-access.md +17 -0
- package/docs/guidelines/agent-infra/recurring-criticism-mechanics.md +96 -0
- package/package.json +1 -1
- package/src/agent-src/scripts/archival_sweep.ts +117 -0
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +19 -3
- package/src/agent-src/scripts/gate_budget.ts +282 -0
- package/src/agent-src/scripts/gate_execute.ts +72 -31
- package/src/agent-src/scripts/roadmap_gates.ts +74 -3
- package/src/agent-src/scripts/update_roadmap_progress.ts +251 -7
- package/src/agent-src/templates/scripts/telemetry/flush_sender.mjs +163 -0
- package/src/agent-src/templates/scripts/telemetry/remote.ts +160 -7
- package/src/agent-src/templates/scripts/telemetry/transport.ts +169 -0
- package/src/config/agent-settings.template.yml +27 -2
- package/src/config/ci-local-parity.yml +18 -0
- package/src/config/estate-count-budget.json +367 -3
- package/src/config/gate-violation-baselines.json +41 -41
- package/src/config/pack-size-budget.json +3 -2
- package/src/config/rule-activation-census.json +14 -6
- package/src/config/rule-stub-ceilings.json +19 -6
- package/src/install/claudePathsPlan.ts +196 -0
- package/src/install/hostLayerFingerprint.ts +145 -0
- package/src/install/partitionEligibility.ts +325 -0
- package/src/scripts/_cli/cmd_session_recycle.ts +9 -1
- package/src/scripts/_dispatch.bash +6 -1
- package/src/scripts/_lib/ac_heading.ts +30 -0
- package/src/scripts/_lib/agent_src.ts +42 -0
- package/src/scripts/_lib/bench_ab_pinned_repo.ts +162 -0
- package/src/scripts/_lib/bench_ab_workspace.ts +10 -1
- package/src/scripts/_lib/envelope_grounding.ts +38 -8
- package/src/scripts/_lib/installed_lock.ts +50 -5
- package/src/scripts/_lib/layer_overlap_notice.ts +85 -0
- package/src/scripts/_lib/md_section.ts +129 -0
- package/src/scripts/_lib/scope_guard.sh +86 -4
- package/src/scripts/_lib/self_repair.ts +19 -0
- package/src/scripts/_lib/self_repair_class_b.ts +171 -0
- package/src/scripts/_lib/subagent_capsule.ts +81 -1
- package/src/scripts/adr/regenerate_index.ts +13 -2
- package/src/scripts/adr_cite_check.ts +424 -0
- package/src/scripts/ai_council/cli_help.ts +55 -0
- package/src/scripts/before_complete_hook.ts +153 -28
- package/src/scripts/bench_ab_v2_complexity.ts +27 -7
- package/src/scripts/check_adr_frontmatter.ts +187 -1
- package/src/scripts/check_references.ts +60 -0
- package/src/scripts/check_release_published.ts +32 -11
- package/src/scripts/check_single_delivery.ts +517 -0
- package/src/scripts/ci_settle.ts +170 -0
- package/src/scripts/condense.ts +20 -170
- package/src/scripts/council_cli.ts +3 -3
- package/src/scripts/dispatch_r2_reviewer.ts +6 -14
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +77 -14
- package/src/scripts/hooks/block_no_verify.ts +61 -10
- package/src/scripts/hooks/block_unauthorized_git.ts +1 -1
- package/src/scripts/hooks/concern_registry.ts +4 -0
- package/src/scripts/hooks/rtk_wrap_hook.ts +8 -3
- package/src/scripts/hooks/run_continuation_hook.ts +887 -68
- package/src/scripts/hooks/ship_diff_volume_hook.ts +81 -15
- package/src/scripts/hooks/source_first_gate_hook.ts +464 -0
- package/src/scripts/hooks/state_io.ts +782 -20
- package/src/scripts/hooks/subagent_ledger_hook.ts +44 -15
- package/src/scripts/hooks/telemetry_flush_hook.ts +139 -0
- package/src/scripts/hooks/telemetry_self_repair.ts +128 -0
- package/src/scripts/hooks/telemetry_usage_hook.ts +22 -4
- package/src/scripts/hooks/turn_end_gate_hook.ts +81 -15
- package/src/scripts/install.sh +12 -2
- package/src/scripts/install.ts +16 -11
- package/src/scripts/language_mirror_hook.ts +561 -25
- package/src/scripts/lint_framework_leakage_allowlist.json +2 -2
- package/src/scripts/lint_plan_risk_register.ts +4 -3
- package/src/scripts/lint_skill_router_head.ts +4 -4
- package/src/scripts/mcp_server/tools.ts +2 -2
- package/src/scripts/memory_consolidation.ts +189 -0
- package/src/scripts/memory_eviction.ts +384 -0
- package/src/scripts/new_roadmap.ts +136 -0
- package/src/scripts/pack_mcp_content.ts +20 -5
- package/src/scripts/release.ts +37 -38
- package/src/scripts/report_layer_overlap.ts +78 -0
- package/src/scripts/report_memory_pointers.ts +622 -0
- package/src/scripts/self_repair_hook.ts +37 -5
- package/src/scripts/session_register_hook.ts +115 -15
- package/src/scripts/skill_usage_collect.ts +11 -1
- package/src/scripts/skill_usage_report.ts +278 -8
- package/src/scripts/sync_pr_branch.ts +7 -0
- package/src/server/schemas/settings.ts +8 -0
- package/dist/cli-delegate/chunk-EJITJBJX.js +0 -172
- package/dist/cli-delegate/chunk-PZWRPRWP.js +0 -38
- /package/dist/cli-delegate/{chunk-RYNADUFG.js → chunk-LWTYTX6F.js} +0 -0
|
@@ -35,8 +35,9 @@
|
|
|
35
35
|
* (`Explore` / `general-purpose` / `production-validator` / …), the same
|
|
36
36
|
* single host string `orchestration_record_hook` already records.
|
|
37
37
|
* - `last_assistant_message` is free model prose and is NEVER recorded, in
|
|
38
|
-
* any form — not truncated, not hashed, not summarised. Only the
|
|
39
|
-
* parse VERDICT (`ok` / `fail` / `
|
|
38
|
+
* any form — not truncated, not hashed, not summarised. Only the four-way
|
|
39
|
+
* parse VERDICT (`ok` / `fail` / `no_message` / `no_envelope`) and a
|
|
40
|
+
* validator error COUNT
|
|
40
41
|
* leave this function. A record type with no field able to hold prose has
|
|
41
42
|
* no scrubber that can fail.
|
|
42
43
|
*
|
|
@@ -196,7 +197,20 @@ function extractParentId(payload: JsonObject, ownId: string | null): string | nu
|
|
|
196
197
|
return null;
|
|
197
198
|
}
|
|
198
199
|
|
|
199
|
-
export type EnvelopeParse = 'ok' | 'fail' | '
|
|
200
|
+
export type EnvelopeParse = 'ok' | 'fail' | 'no_message' | 'no_envelope';
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* The retired fifth value. Records written before 2026-08-20 carry `absent`,
|
|
204
|
+
* which collapsed `no_message` and `no_envelope` into one bucket.
|
|
205
|
+
*
|
|
206
|
+
* It is deliberately NOT in the union. Nothing reads `envelope_parse` back
|
|
207
|
+
* programmatically — the only writer is this file and the only reader is a
|
|
208
|
+
* human or a `grep` — so there is no consumer to keep compatible, and leaving
|
|
209
|
+
* the value writable would let the collapse reappear. A reader of the
|
|
210
|
+
* historical window must treat `absent` as "one of the two, unknown which":
|
|
211
|
+
* the measurement it supports starts at the split, not before it.
|
|
212
|
+
*/
|
|
213
|
+
export const RETIRED_ENVELOPE_PARSE = 'absent';
|
|
200
214
|
|
|
201
215
|
export interface ParseVerdict {
|
|
202
216
|
verdict: EnvelopeParse;
|
|
@@ -207,18 +221,33 @@ export interface ParseVerdict {
|
|
|
207
221
|
/**
|
|
208
222
|
* Classify a subagent's final message against the response-envelope contract.
|
|
209
223
|
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
224
|
+
* Four outcomes, and every boundary between them separates two defects with
|
|
225
|
+
* two different fixes:
|
|
226
|
+
*
|
|
227
|
+
* - `no_message` — the host delivered no `last_assistant_message` at all
|
|
228
|
+
* (null, or whitespace only). This is the return-channel loss #58109
|
|
229
|
+
* describes, and it is the ONLY verdict a disk-envelope fallback may key
|
|
230
|
+
* on.
|
|
231
|
+
* - `no_envelope` — a message arrived and carried no JSON object. This is
|
|
232
|
+
* the overwhelmingly common case: subagents answer in prose. It is not a
|
|
233
|
+
* channel failure, and a fallback keyed on it would fire on nearly every
|
|
234
|
+
* dispatch.
|
|
235
|
+
* - `fail` — an object arrived and did not satisfy `validateResponse`.
|
|
236
|
+
* - `ok` — an object arrived and validated.
|
|
237
|
+
*
|
|
238
|
+
* The first two used to be one value, `absent`. Over the first measured window
|
|
239
|
+
* that collapse read 25 of 25 `absent` — including the #58109 control arm that
|
|
240
|
+
* returned a complete report — so a rate computed off it measured the answer
|
|
241
|
+
* format rather than the channel
|
|
242
|
+
* (`agents/evidence/investigations/subagent-lifecycle-phase0-return-channel.md`
|
|
243
|
+
* § F2). Splitting them is what makes the Phase-1 baseline's return-rate
|
|
244
|
+
* column, and Phase 2's fallback condition, expressible at all.
|
|
216
245
|
*/
|
|
217
246
|
export function classifyEnvelope(message: string | null): ParseVerdict {
|
|
218
|
-
if (message === null || !message.trim()) return { verdict: '
|
|
247
|
+
if (message === null || !message.trim()) return { verdict: 'no_message', error_count: 0 };
|
|
219
248
|
|
|
220
249
|
const candidates = _jsonObjectCandidates(message);
|
|
221
|
-
if (candidates.length === 0) return { verdict: '
|
|
250
|
+
if (candidates.length === 0) return { verdict: 'no_envelope', error_count: 0 };
|
|
222
251
|
|
|
223
252
|
// A message may carry several object-shaped spans. The envelope is the one
|
|
224
253
|
// that validates, so a `fail` is only reported once every candidate has
|
|
@@ -229,7 +258,7 @@ export function classifyEnvelope(message: string | null): ParseVerdict {
|
|
|
229
258
|
if (result.valid) return { verdict: 'ok', error_count: 0 };
|
|
230
259
|
if (best === null) best = { verdict: 'fail', error_count: result.errors.length };
|
|
231
260
|
}
|
|
232
|
-
return best ?? { verdict: '
|
|
261
|
+
return best ?? { verdict: 'no_envelope', error_count: 0 };
|
|
233
262
|
}
|
|
234
263
|
|
|
235
264
|
/**
|
|
@@ -239,9 +268,9 @@ export function classifyEnvelope(message: string | null): ParseVerdict {
|
|
|
239
268
|
* R2 finding 4: the previous implementation took the span from the first `{`
|
|
240
269
|
* to the LAST `}`, so a valid envelope followed by any later brace in prose
|
|
241
270
|
* ("… — done. See {done}.") produced unparseable text and was reported
|
|
242
|
-
* `
|
|
243
|
-
* bucket that the
|
|
244
|
-
*
|
|
271
|
+
* `no_envelope` — routing an extraction failure into the "answered in prose"
|
|
272
|
+
* bucket that the verdict split exists to keep separate, and biasing the
|
|
273
|
+
* Phase-1 return-rate falsifier downward.
|
|
245
274
|
*
|
|
246
275
|
* R2 finding 11: fenced blocks are now matched with an anchored, global scan
|
|
247
276
|
* rather than a first-match-anywhere regex, which used to be able to match at
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Session-end Class-A flush (road-to-org-telemetry Phase 2, step 2.1).
|
|
4
|
+
*
|
|
5
|
+
* WHAT THIS CONCERN DOES, IN ONE SENTENCE: it checks whether the outbound
|
|
6
|
+
* spool has bytes and, if so, spawns a detached sender and returns. It makes
|
|
7
|
+
* no network call of its own, in this process, ever.
|
|
8
|
+
*
|
|
9
|
+
* THAT SHAPE IS MEASURED, NOT PREFERRED. Phase 0's second spike
|
|
10
|
+
* pre-registered "added latency at or below one second at p95" for an INLINE
|
|
11
|
+
* session-end flush and measured it failing: 0.4 ms against a healthy sink,
|
|
12
|
+
* 0.3 ms against a refused connection, and **1002 ms against a blackhole** —
|
|
13
|
+
* a socket that accepts and never answers, which is the shape a wedged sink
|
|
14
|
+
* actually takes. Against the same blackhole the detached spool measured
|
|
15
|
+
* 20.5 ms p95, dominated by process spawn. The step's own text defers to that
|
|
16
|
+
* result, so this concern is the spool half of it.
|
|
17
|
+
*
|
|
18
|
+
* DEFAULT-OFF, AND `enabled` IS NOT THE SWITCH. `read_remote_settings`
|
|
19
|
+
* resolves `active` only when `enabled` is true AND the org pack supplied an
|
|
20
|
+
* endpoint, an org id and a salt — none of which has a default and none of
|
|
21
|
+
* which this public repository ships a value for. A clone therefore cannot
|
|
22
|
+
* reach the spawn path. `flush: never` (the value an org sets to keep records
|
|
23
|
+
* local) short-circuits before the spool is even stat-ed.
|
|
24
|
+
*
|
|
25
|
+
* ZERO FILE OPERATIONS WHEN INACTIVE, and the spool `stat` is the only one
|
|
26
|
+
* when active-but-idle: most sessions invoke no skill, so there is no spool
|
|
27
|
+
* to drain and the concern costs one `stat` and returns.
|
|
28
|
+
*
|
|
29
|
+
* EXIT CODE IS ALWAYS 0 and stdout is always empty. A hook `warn` (exit 2) is
|
|
30
|
+
* read as a hard BLOCK on the verified host, and a telemetry concern has
|
|
31
|
+
* nothing to say to the model — least of all at session end, where a message
|
|
32
|
+
* would arrive after the last thing the user reads.
|
|
33
|
+
*/
|
|
34
|
+
import * as fs from 'node:fs';
|
|
35
|
+
import * as path from 'node:path';
|
|
36
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
37
|
+
|
|
38
|
+
import {
|
|
39
|
+
DEFAULT_FLUSH_TIMEOUT_MS,
|
|
40
|
+
FLUSH_SESSION_END,
|
|
41
|
+
spawn_detached_sender,
|
|
42
|
+
spool_has_work,
|
|
43
|
+
spool_path_for,
|
|
44
|
+
} from '../../agent-src/templates/scripts/telemetry/transport.js';
|
|
45
|
+
import { is_replay_mode } from './state_io.js';
|
|
46
|
+
import { readHookStdin } from './hook_stdin.js';
|
|
47
|
+
import { _resolveRoot, readSettingsFor } from './telemetry_usage_hook.js';
|
|
48
|
+
|
|
49
|
+
const EXIT_ALLOW = 0;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Where `flush_sender.mjs` lives, relative to this module.
|
|
53
|
+
*
|
|
54
|
+
* Resolved from `import.meta.url` and then existence-checked, rather than
|
|
55
|
+
* assumed: under an esbuild bundle every module shares the bundle's own URL,
|
|
56
|
+
* so this path can be wrong. A wrong path must degrade to "no flush this
|
|
57
|
+
* session" (the records stay spooled and go out next time) and never to a
|
|
58
|
+
* spawn of something that is not the sender.
|
|
59
|
+
*/
|
|
60
|
+
export const SENDER_REL = '../../agent-src/templates/scripts/telemetry/flush_sender.mjs';
|
|
61
|
+
|
|
62
|
+
export function resolveSenderScript(): string | null {
|
|
63
|
+
try {
|
|
64
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
65
|
+
const candidate = path.resolve(here, SENDER_REL);
|
|
66
|
+
return fs.statSync(candidate).isFile() ? candidate : null;
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface FlushOutcome {
|
|
73
|
+
/** `spawned` is the only outcome that starts a process. */
|
|
74
|
+
readonly result: 'inactive' | 'flush-never' | 'empty-spool' | 'no-sender' | 'replay' | 'spawned';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function flushFor(consumer_root: string, sender: string | null = resolveSenderScript()): FlushOutcome {
|
|
78
|
+
try {
|
|
79
|
+
const { settings, root } = readSettingsFor(consumer_root);
|
|
80
|
+
if (!settings.active) return { result: 'inactive' };
|
|
81
|
+
if (settings.flush !== FLUSH_SESSION_END) return { result: 'flush-never' };
|
|
82
|
+
if (is_replay_mode()) return { result: 'replay' };
|
|
83
|
+
|
|
84
|
+
// Relative to the PROJECT root, matching the appender exactly — a
|
|
85
|
+
// flush that resolved the log against the session cwd would drain a
|
|
86
|
+
// spool nobody writes.
|
|
87
|
+
const logPath = path.isAbsolute(settings.log_path)
|
|
88
|
+
? settings.log_path
|
|
89
|
+
: path.join(root, settings.log_path);
|
|
90
|
+
const spool = spool_path_for(logPath);
|
|
91
|
+
if (!spool_has_work(spool)) return { result: 'empty-spool' };
|
|
92
|
+
if (sender === null) return { result: 'no-sender' };
|
|
93
|
+
|
|
94
|
+
const ok = spawn_detached_sender({
|
|
95
|
+
sender_script: sender,
|
|
96
|
+
spool_path: spool,
|
|
97
|
+
endpoint: settings.endpoint,
|
|
98
|
+
timeout_ms: DEFAULT_FLUSH_TIMEOUT_MS,
|
|
99
|
+
});
|
|
100
|
+
return { result: ok ? 'spawned' : 'no-sender' };
|
|
101
|
+
} catch {
|
|
102
|
+
return { result: 'inactive' };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function run(stdin_text: string, options: { consumer_root: string }): number {
|
|
107
|
+
flushFor(options.consumer_root);
|
|
108
|
+
return EXIT_ALLOW;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function main(): number {
|
|
112
|
+
const raw = readHookStdin();
|
|
113
|
+
let envelope: unknown = {};
|
|
114
|
+
try {
|
|
115
|
+
envelope = raw.trim() ? JSON.parse(raw) : {};
|
|
116
|
+
} catch {
|
|
117
|
+
envelope = {};
|
|
118
|
+
}
|
|
119
|
+
flushFor(_resolveRoot(envelope as never));
|
|
120
|
+
return EXIT_ALLOW;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Bundle-safety: never auto-run when inlined into an esbuild bundle, where
|
|
124
|
+
// every module shares the bundle's `import.meta.url` (see cmd_migrate.ts).
|
|
125
|
+
declare const __AGENT_CONFIG_BUNDLE__: boolean | undefined;
|
|
126
|
+
function _isCliEntry(): boolean {
|
|
127
|
+
if (typeof __AGENT_CONFIG_BUNDLE__ !== 'undefined' && __AGENT_CONFIG_BUNDLE__) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
if (process.argv[1] === undefined) return false;
|
|
131
|
+
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
132
|
+
if (import.meta.url === argvUrl) return true;
|
|
133
|
+
try {
|
|
134
|
+
return fs.realpathSync(fileURLToPath(import.meta.url)) === fs.realpathSync(path.resolve(process.argv[1]));
|
|
135
|
+
} catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (_isCliEntry()) process.exit(main());
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Class-A self-repair shadow (road-to-org-telemetry Phase 5, step 5.1).
|
|
3
|
+
*
|
|
4
|
+
* One function, called from `self_repair_hook.ts` immediately after a defect
|
|
5
|
+
* is queued. It emits a structural record saying that a defect of a named
|
|
6
|
+
* class occurred — never what happened.
|
|
7
|
+
*
|
|
8
|
+
* WHY THIS IS NOT A CONCERN OF ITS OWN. The event it reacts to is not a host
|
|
9
|
+
* event; it is "the self-repair store just accepted a record". Binding a
|
|
10
|
+
* second concern to the same two slots would re-run the detectors to find out
|
|
11
|
+
* whether the first one wrote anything, so the emission belongs at the seam
|
|
12
|
+
* where the answer is already in hand.
|
|
13
|
+
*
|
|
14
|
+
* WHAT IT CANNOT LEAK, BY CONSTRUCTION. `ClassADefectRecord` has no field able
|
|
15
|
+
* to hold free-form text. `DefectFinding.evidence` (a quoted span of the
|
|
16
|
+
* offending text) and `suggested_surface` (a free sentence) are the Class-B
|
|
17
|
+
* payload and are not passed to the builder at all — not sanitized, not
|
|
18
|
+
* truncated, not optional. There is no scrubber here because there is nothing
|
|
19
|
+
* for a scrubber to fail on.
|
|
20
|
+
*
|
|
21
|
+
* DEFAULT-OFF on the same terms as every other telemetry writer: `active`
|
|
22
|
+
* needs `enabled` AND an endpoint AND an org id AND a salt, none of which this
|
|
23
|
+
* public repository ships a value for. Inactive means zero file operations.
|
|
24
|
+
*
|
|
25
|
+
* NEVER THROWS. The caller wraps it, and the store write has already
|
|
26
|
+
* succeeded by the time it runs; a telemetry failure must not cost the user
|
|
27
|
+
* their turn or their queued defect.
|
|
28
|
+
*/
|
|
29
|
+
import * as os from 'node:os';
|
|
30
|
+
import * as path from 'node:path';
|
|
31
|
+
|
|
32
|
+
import {
|
|
33
|
+
append_class_a_record,
|
|
34
|
+
build_class_a_defect_record,
|
|
35
|
+
} from '../../agent-src/templates/scripts/telemetry/remote.js';
|
|
36
|
+
import {
|
|
37
|
+
FLUSH_SESSION_END,
|
|
38
|
+
spool_path_for,
|
|
39
|
+
} from '../../agent-src/templates/scripts/telemetry/transport.js';
|
|
40
|
+
import { DEFECT_CLASSES, type DefectRecord } from '../_lib/self_repair.js';
|
|
41
|
+
import { is_replay_mode } from './state_io.js';
|
|
42
|
+
import {
|
|
43
|
+
extractDisciplineProfile,
|
|
44
|
+
extractPackageVersion,
|
|
45
|
+
readSettingsFor,
|
|
46
|
+
} from './telemetry_usage_hook.js';
|
|
47
|
+
|
|
48
|
+
type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue };
|
|
49
|
+
type JsonObject = { [k: string]: JsonValue };
|
|
50
|
+
|
|
51
|
+
function str(v: JsonValue | undefined): string {
|
|
52
|
+
return typeof v === 'string' ? v : '';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Host id off the dispatcher envelope, or `null` when it carried none. */
|
|
56
|
+
export function hostOf(env: JsonObject): string | null {
|
|
57
|
+
const v = str(env['platform']) || str(env['host']);
|
|
58
|
+
return v === '' ? null : v;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The host session id, hashed downstream and never recorded raw. */
|
|
62
|
+
export function sessionIdOf(env: JsonObject): string {
|
|
63
|
+
return str(env['session_id']) || str(env['sessionId']);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type ShadowOutcome =
|
|
67
|
+
| 'inactive'
|
|
68
|
+
| 'replay'
|
|
69
|
+
| 'unknown-class'
|
|
70
|
+
| 'written'
|
|
71
|
+
| 'failed';
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Emit the shadow for one queued defect. Returns what it did, for tests —
|
|
75
|
+
* the caller ignores it.
|
|
76
|
+
*/
|
|
77
|
+
export function emitDefectShadow(
|
|
78
|
+
root: string,
|
|
79
|
+
rec: DefectRecord,
|
|
80
|
+
env: JsonObject,
|
|
81
|
+
): ShadowOutcome {
|
|
82
|
+
try {
|
|
83
|
+
const { settings, text, root: projectRoot } = readSettingsFor(root);
|
|
84
|
+
if (!settings.active) return 'inactive';
|
|
85
|
+
if (is_replay_mode()) return 'replay';
|
|
86
|
+
|
|
87
|
+
// The vocabulary is pinned in `self_repair.ts` and mirrored into the
|
|
88
|
+
// upstream issue form by test. A class outside it means a detector was
|
|
89
|
+
// added without the rest of its registration; recording it would put
|
|
90
|
+
// an unpinned value into an org's record set, so it is dropped and the
|
|
91
|
+
// local queue keeps the defect regardless.
|
|
92
|
+
if (!(DEFECT_CLASSES as readonly string[]).includes(rec.defect_class)) {
|
|
93
|
+
return 'unknown-class';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const record = build_class_a_defect_record({
|
|
97
|
+
defect_class: rec.defect_class,
|
|
98
|
+
defect_source: rec.source,
|
|
99
|
+
occurrences: rec.occurrences,
|
|
100
|
+
host: hostOf(env),
|
|
101
|
+
org_id: settings.org_id,
|
|
102
|
+
salt: settings.salt,
|
|
103
|
+
hostname: os.hostname(),
|
|
104
|
+
username: os.userInfo().username,
|
|
105
|
+
session_id: sessionIdOf(env),
|
|
106
|
+
package_version: extractPackageVersion(process.env),
|
|
107
|
+
discipline_profile: extractDisciplineProfile(text),
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const logPath = path.isAbsolute(settings.log_path)
|
|
111
|
+
? settings.log_path
|
|
112
|
+
: path.join(projectRoot, settings.log_path);
|
|
113
|
+
const spool = settings.flush === FLUSH_SESSION_END ? spool_path_for(logPath) : null;
|
|
114
|
+
append_class_a_record(
|
|
115
|
+
logPath,
|
|
116
|
+
record,
|
|
117
|
+
{
|
|
118
|
+
max_age_days: settings.retention_max_age_days,
|
|
119
|
+
max_bytes: settings.retention_max_bytes,
|
|
120
|
+
},
|
|
121
|
+
new Date(),
|
|
122
|
+
spool,
|
|
123
|
+
);
|
|
124
|
+
return 'written';
|
|
125
|
+
} catch {
|
|
126
|
+
return 'failed';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -51,6 +51,10 @@ import {
|
|
|
51
51
|
build_class_a_record,
|
|
52
52
|
profile_from_legacy_tier,
|
|
53
53
|
} from '../../agent-src/templates/scripts/telemetry/remote.js';
|
|
54
|
+
import {
|
|
55
|
+
FLUSH_SESSION_END,
|
|
56
|
+
spool_path_for,
|
|
57
|
+
} from '../../agent-src/templates/scripts/telemetry/transport.js';
|
|
54
58
|
import { is_replay_mode } from './state_io.js';
|
|
55
59
|
import { readHookStdin } from './hook_stdin.js';
|
|
56
60
|
|
|
@@ -271,14 +275,28 @@ function processEnvelope(envelope: JsonValue, consumer_root: string): number {
|
|
|
271
275
|
const logPath = path.isAbsolute(settings.log_path)
|
|
272
276
|
? settings.log_path
|
|
273
277
|
: path.join(root, settings.log_path);
|
|
278
|
+
// The outbound spool (Phase 2, step 2.1) is written HERE, by the same
|
|
279
|
+
// call that logs the record, and only when the install declared a
|
|
280
|
+
// flush. Under `flush: never` there is no transport at all, so a spool
|
|
281
|
+
// would be a file that grows and is never drained. The path is derived
|
|
282
|
+
// rather than configured so an install cannot point the spool
|
|
283
|
+
// somewhere the appender does not write — a failure whose symptom
|
|
284
|
+
// (records logged locally, nothing ever sent) is invisible.
|
|
285
|
+
const spool = settings.flush === FLUSH_SESSION_END ? spool_path_for(logPath) : null;
|
|
274
286
|
// The growth budget travels from the settings the install actually
|
|
275
287
|
// declared, not from the appender's own defaults — a retention key
|
|
276
288
|
// the write path never reads would be a setting that decorates
|
|
277
289
|
// rather than decides.
|
|
278
|
-
append_class_a_record(
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
290
|
+
append_class_a_record(
|
|
291
|
+
logPath,
|
|
292
|
+
record,
|
|
293
|
+
{
|
|
294
|
+
max_age_days: settings.retention_max_age_days,
|
|
295
|
+
max_bytes: settings.retention_max_bytes,
|
|
296
|
+
},
|
|
297
|
+
new Date(),
|
|
298
|
+
spool,
|
|
299
|
+
);
|
|
282
300
|
} catch {
|
|
283
301
|
// Malformed payload, unreadable disk, a rejected id — never block
|
|
284
302
|
// the tool call, and never write a degraded record.
|
|
@@ -146,17 +146,25 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
146
146
|
|
|
147
147
|
import {
|
|
148
148
|
classify,
|
|
149
|
+
hasStableSessionId,
|
|
149
150
|
isSyntheticPrompt,
|
|
150
|
-
|
|
151
|
+
statePathFor as languageStatePathFor,
|
|
151
152
|
type Verdict,
|
|
152
153
|
} from '../language_mirror_hook.js';
|
|
153
|
-
// Round 7 § Phase 1 — the CI-settle producer. Imported for its
|
|
154
|
-
//
|
|
155
|
-
|
|
154
|
+
// Round 7 § Phase 1 — the CI-settle producer. Imported for its path BUILDER, not
|
|
155
|
+
// for a path constant: "the consumer cannot read a path the producer does not
|
|
156
|
+
// write" is only true while the two agree, and importing a constant made that
|
|
157
|
+
// agreement invisible when the producer's layout moved. A builder makes the move
|
|
158
|
+
// a type error.
|
|
159
|
+
import { statePathFor as ciStatePathFor } from '../before_complete_hook.js';
|
|
156
160
|
import { isSafeTranscriptPath } from './end_review_nudge_hook.js';
|
|
157
161
|
import { unwrap, type JsonObject, type JsonValue } from './envelope.js';
|
|
158
162
|
import { readHookStdin } from './hook_stdin.js';
|
|
159
|
-
import {
|
|
163
|
+
import {
|
|
164
|
+
atomic_write_json,
|
|
165
|
+
is_replay_mode,
|
|
166
|
+
owns_session_state as ownsSessionState,
|
|
167
|
+
} from './state_io.js';
|
|
160
168
|
import { openRecordStats } from './subagent_ledger_hook.js';
|
|
161
169
|
import {
|
|
162
170
|
deriveSessionKey,
|
|
@@ -400,7 +408,17 @@ export interface LanguagePin {
|
|
|
400
408
|
*
|
|
401
409
|
* So the producer is `before_complete_hook`, which already sees tool output on
|
|
402
410
|
* `post_tool_use` and already owns the `pendingCount` predicate. It writes
|
|
403
|
-
* `ci_last` into
|
|
411
|
+
* `ci_last` into its own per-session state file under
|
|
412
|
+
* `agents/state/verify-before-complete/` and this reads it — via the producer's
|
|
413
|
+
* `statePathFor`, never a path literal, so the next move of that layout is a
|
|
414
|
+
* compile error instead of a detector that silently reads nothing. (That is not
|
|
415
|
+
* hypothetical: the sibling language pin moved exactly this way and its
|
|
416
|
+
* consumer here kept importing the abandoned constant, which turned a blocking
|
|
417
|
+
* detector off without a single failing test.)
|
|
418
|
+
*
|
|
419
|
+
* KEYED ON `session_id`, because the CI witness is a fact about ONE run.
|
|
420
|
+
* A no-id envelope reads as "no CI observed" — the producer persists nothing in
|
|
421
|
+
* that case, and the refusal direction that follows is the safe one.
|
|
404
422
|
*
|
|
405
423
|
* NO network call, deliberately. Asking `gh pr checks` here would put a network
|
|
406
424
|
* round-trip on every turn-end; `road-to-hook-latency-repair` exists because that
|
|
@@ -412,10 +430,20 @@ export interface LanguagePin {
|
|
|
412
430
|
* never saw. A stale NEGATIVE ("CI was not settled") only ever refuses more often.
|
|
413
431
|
* Same freshness invariant, opposite failure direction.
|
|
414
432
|
*/
|
|
415
|
-
export function readCiSettled(
|
|
433
|
+
export function readCiSettled(
|
|
434
|
+
workspaceRoot: string,
|
|
435
|
+
session_id: string,
|
|
436
|
+
): { seen: boolean; settled: boolean } {
|
|
437
|
+
if (!hasStableSessionId(session_id)) return { seen: false, settled: false };
|
|
416
438
|
try {
|
|
417
|
-
const raw = fs.readFileSync(path.join(workspaceRoot,
|
|
439
|
+
const raw = fs.readFileSync(path.join(workspaceRoot, ciStatePathFor(session_id)), 'utf-8');
|
|
418
440
|
const decoded: unknown = JSON.parse(raw);
|
|
441
|
+
// Same ownership check as the pin, and here it is the sharper of the two:
|
|
442
|
+
// a FOREIGN `ci_last: {settled: true}` vouches for a CI run this session
|
|
443
|
+
// never made, which is exactly the premature completion claim detector D
|
|
444
|
+
// exists to refuse. Refusing an unowned file returns "no CI observed",
|
|
445
|
+
// which never refuses a session for someone else's run.
|
|
446
|
+
if (!ownsSessionState(decoded, session_id)) return { seen: false, settled: false };
|
|
419
447
|
if (typeof decoded === 'object' && decoded !== null && !Array.isArray(decoded)) {
|
|
420
448
|
const ci = (decoded as Record<string, unknown>)['ci_last'];
|
|
421
449
|
if (typeof ci === 'object' && ci !== null && !Array.isArray(ci)) {
|
|
@@ -496,11 +524,43 @@ export function detectCompletionClaim(
|
|
|
496
524
|
*
|
|
497
525
|
* (R2 finding 8: this line was left 73 lines above, where inserting `readCiSettled`
|
|
498
526
|
* had stranded it — so it documented the wrong function and this one had none.)
|
|
527
|
+
*
|
|
528
|
+
* KEYED ON `session_id`, because the producer is. The import above says
|
|
529
|
+
* "imported for its STATE_FILE only, so the consumer cannot read a path the
|
|
530
|
+
* producer does not write" — and that discipline failed the moment the producer
|
|
531
|
+
* MOVED: `language_mirror_hook` split its state per session
|
|
532
|
+
* (`agents/state/language-mirror/<digest>.json`) and `_pruneLegacyState` now
|
|
533
|
+
* deletes the single file this function used to read. Importing the old symbol
|
|
534
|
+
* still compiled, so nothing broke loudly: `readLanguagePin` returned `und` for
|
|
535
|
+
* every turn, `detectLanguage` returned `null` for every turn, and detector B of
|
|
536
|
+
* a blocking gate stopped checking anything. Importing the path-BUILDER instead
|
|
537
|
+
* of a path constant is what makes the next such move a type error rather than a
|
|
538
|
+
* silent dead detector.
|
|
539
|
+
*
|
|
540
|
+
* NO LEGACY FALLBACK, deliberately. Reading `STATE_FILE` when the per-session
|
|
541
|
+
* file is absent would restore exactly the cross-session read the split closed:
|
|
542
|
+
* that file is shared by every session under one project root, so a neighbouring
|
|
543
|
+
* English session's pin would become this German session's obligation. A missing
|
|
544
|
+
* pin is the safe answer; a foreign pin is the defect.
|
|
545
|
+
*
|
|
546
|
+
* No stable id ⇒ `und`. The producer runs stateless in that case and persists
|
|
547
|
+
* nothing, so there is no pin to read — not "no obligation by accident", but the
|
|
548
|
+
* same degradation the producer documents, in the same direction (under-refuse).
|
|
499
549
|
*/
|
|
500
|
-
export function readLanguagePin(workspaceRoot: string): Verdict {
|
|
550
|
+
export function readLanguagePin(workspaceRoot: string, session_id: string): Verdict {
|
|
551
|
+
if (!hasStableSessionId(session_id)) return 'und';
|
|
501
552
|
try {
|
|
502
|
-
const raw = fs.readFileSync(
|
|
553
|
+
const raw = fs.readFileSync(
|
|
554
|
+
path.join(workspaceRoot, languageStatePathFor(session_id)),
|
|
555
|
+
'utf-8',
|
|
556
|
+
);
|
|
503
557
|
const decoded: unknown = JSON.parse(raw);
|
|
558
|
+
// The digest path is not the whole guarantee — see `owns_session_state`.
|
|
559
|
+
// A file that reached this pathname by a copy, a restore, or a buggy
|
|
560
|
+
// writer still carries its real owner, and consuming it hands one
|
|
561
|
+
// session's pin to another. Refusing it costs a turn's obligation;
|
|
562
|
+
// accepting it is the cross-session read the split closed.
|
|
563
|
+
if (!ownsSessionState(decoded, session_id)) return 'und';
|
|
504
564
|
if (typeof decoded === 'object' && decoded !== null && !Array.isArray(decoded)) {
|
|
505
565
|
const lang = (decoded as Record<string, unknown>)['language'];
|
|
506
566
|
if (lang === 'de' || lang === 'en') return lang;
|
|
@@ -1019,9 +1079,13 @@ export function main(): number {
|
|
|
1019
1079
|
// than colliding, and this one collides. It is named here rather than
|
|
1020
1080
|
// papered over — the degradation is toward UNDER-refusing, which is the safe
|
|
1021
1081
|
// direction, and layer 1 still covers the host that sends the flag.
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1082
|
+
// RAW id, kept beside the derived key rather than replaced by it:
|
|
1083
|
+
// `readLanguagePin` addresses the producer's own per-session file, and the
|
|
1084
|
+
// producer keys that on the raw `session_id`. Passing `sessionKey` here would
|
|
1085
|
+
// read a path nothing writes — the same shape as the STATE_FILE break this
|
|
1086
|
+
// parameter exists to close.
|
|
1087
|
+
const rawSessionId = str(envelope['session_id'] as JsonValue | undefined) || '';
|
|
1088
|
+
const sessionKey = deriveSessionKey(rawSessionId || 'unknown-session');
|
|
1025
1089
|
if (alreadyRefusedTurn(workspaceRoot, sessionKey, turnOrdinal)) return EXIT_ALLOW;
|
|
1026
1090
|
|
|
1027
1091
|
// B and C run on every turn-end; A and D run only when no dispatch is open
|
|
@@ -1042,7 +1106,7 @@ export function main(): number {
|
|
|
1042
1106
|
// (Phase 3 Step 2, narrowed by R2 round 2 finding 2). B and C are not
|
|
1043
1107
|
// excused by anything about a dispatch and run unchanged.
|
|
1044
1108
|
dispatchOpen ? null : detectPromissory(lastAssistant),
|
|
1045
|
-
detectLanguage(lastAssistant, readLanguagePin(workspaceRoot)),
|
|
1109
|
+
detectLanguage(lastAssistant, readLanguagePin(workspaceRoot, rawSessionId)),
|
|
1046
1110
|
detectUnverifiedEdit(toolCalls),
|
|
1047
1111
|
// Round 7 § Phase 1 — detector D. It is NOT unconditional, and this
|
|
1048
1112
|
// comment said it was while sitting one line above the `dispatchOpen`
|
|
@@ -1054,7 +1118,9 @@ export function main(): number {
|
|
|
1054
1118
|
// gating is where the comment above says gating belongs — inside the
|
|
1055
1119
|
// detector: no CI observed, or a settled read, or no completion claim
|
|
1056
1120
|
// ⇒ no finding.
|
|
1057
|
-
dispatchOpen
|
|
1121
|
+
dispatchOpen
|
|
1122
|
+
? null
|
|
1123
|
+
: detectCompletionClaim(lastAssistant, readCiSettled(workspaceRoot, rawSessionId)),
|
|
1058
1124
|
]) {
|
|
1059
1125
|
if (f) findings.push(f);
|
|
1060
1126
|
}
|
package/src/scripts/install.sh
CHANGED
|
@@ -783,8 +783,18 @@ run_scope_guard() {
|
|
|
783
783
|
if [[ "$verdict" == "WARN" ]]; then
|
|
784
784
|
$QUIET || {
|
|
785
785
|
echo ""
|
|
786
|
-
echo " ⚠️ Cross-scope install detected
|
|
787
|
-
echo "
|
|
786
|
+
echo " ⚠️ Cross-scope install detected — same version, so no drift, but NOT free:"
|
|
787
|
+
echo " the host loads both registrations with no dedup, so every shared entry"
|
|
788
|
+
echo " below is delivered TWICE in every session."
|
|
789
|
+
# Field 6 is the overlap count; -1 means it could not be taken (a
|
|
790
|
+
# single-file probe such as copilot), which is printed as such rather
|
|
791
|
+
# than rendered as zero.
|
|
792
|
+
echo "$output" | awk -F'\t' '$1=="WARN" {
|
|
793
|
+
if (NF >= 6 && $6 != "-1") printf " - %s at %s (v%s) — %s entr%s in both scopes\n", $2, $3, $4, $6, ($6 == 1 ? "y" : "ies");
|
|
794
|
+
else printf " - %s at %s (v%s) — overlap not counted (this scope is empty or unreadable; a first install here has nothing to double yet)\n", $2, $3, $4;
|
|
795
|
+
}'
|
|
796
|
+
echo ""
|
|
797
|
+
echo " Pick one scope if you can: ADR-236 (one artefact, one layer)."
|
|
788
798
|
echo ""
|
|
789
799
|
}
|
|
790
800
|
return 0
|
package/src/scripts/install.ts
CHANGED
|
@@ -96,6 +96,7 @@ import {
|
|
|
96
96
|
ruleScopeFromSettings,
|
|
97
97
|
type RuleScope,
|
|
98
98
|
} from '../install/rule_scope.js';
|
|
99
|
+
import { isExclusivelyPackageOnly, stampHostLayerFingerprint } from '../install/partitionEligibility.js'; // ADR-236
|
|
99
100
|
import { RULE_SOURCE_REL } from '../install/wizard-plan.js';
|
|
100
101
|
import { flattenSurface, computeSurfaceDelta, type SettingsSurface } from '../shared/settingsSurface.js';
|
|
101
102
|
import { settingsSchema } from '../server/schemas/settings.js';
|
|
@@ -3635,7 +3636,8 @@ function _rule_filter_for_source(
|
|
|
3635
3636
|
scope: RuleScope,
|
|
3636
3637
|
): global_deploy_inventory.FileFilter | null {
|
|
3637
3638
|
if (src_rel !== RULE_SOURCE_REL) return null;
|
|
3638
|
-
return (srcFile: string): boolean =>
|
|
3639
|
+
return (srcFile: string): boolean =>
|
|
3640
|
+
ruleFileArrives(srcFile, scope) && !isExclusivelyPackageOnly(srcFile);
|
|
3639
3641
|
}
|
|
3640
3642
|
|
|
3641
3643
|
/**
|
|
@@ -3804,19 +3806,21 @@ function install_global(
|
|
|
3804
3806
|
(tool_id) => (deploy_results[tool_id] as DeployResult)[2] === 'deploy_failed',
|
|
3805
3807
|
),
|
|
3806
3808
|
);
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
}
|
|
3809
|
+
const corrected_tools =
|
|
3810
|
+
failed_tools.size > 0 ? merged_tools.filter((t) => !failed_tools.has(t)) : merged_tools;
|
|
3811
|
+
if (failed_tools.size > 0 && !arrayStrEqual(corrected_tools, merged_tools)) {
|
|
3812
|
+
installed_lock.write_lockfile(installed_version, corrected_tools, { path: write_path });
|
|
3813
|
+
if (!state.QUIET) {
|
|
3814
|
+
warn(
|
|
3815
|
+
'Lockfile corrected after deploy postcheck — dropped ' +
|
|
3816
|
+
`${[...failed_tools].sort().join(', ')} (verification failed).`,
|
|
3817
|
+
);
|
|
3817
3818
|
}
|
|
3818
3819
|
}
|
|
3819
3820
|
|
|
3821
|
+
stampHostLayerFingerprint(installed_version, corrected_tools, write_path,
|
|
3822
|
+
failed_tools.has('claude-code'), (m) => { if (!state.QUIET) info(m); });
|
|
3823
|
+
|
|
3820
3824
|
if (state.PROGRESS_NDJSON) {
|
|
3821
3825
|
const ordered = Object.keys(deploy_results).sort();
|
|
3822
3826
|
const total = ordered.length;
|
|
@@ -5457,5 +5461,6 @@ export {
|
|
|
5457
5461
|
_copy_dir_dereferencing_symlinks,
|
|
5458
5462
|
_preview_global_reap,
|
|
5459
5463
|
_resolve_global_rule_scope,
|
|
5464
|
+
_rule_filter_for_source,
|
|
5460
5465
|
};
|
|
5461
5466
|
export type { Options, PackRecord, DeployResult };
|