@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
|
@@ -12,13 +12,29 @@
|
|
|
12
12
|
# Verdicts (per the roadmap contract):
|
|
13
13
|
# OK — no install at the other scope; the install can proceed.
|
|
14
14
|
# WARN — install at the other scope, SAME version as the one being installed.
|
|
15
|
-
#
|
|
15
|
+
# No drift — and NOT free. The host loads both registrations with no
|
|
16
|
+
# dedup, so every shared artefact is delivered twice. This verdict used
|
|
17
|
+
# to read "Same content; duplicate registration but no drift", which
|
|
18
|
+
# classified duplication by DRIFT RISK and never by CONTEXT COST, and
|
|
19
|
+
# so taught every reader that identical copies cost nothing. Measured
|
|
20
|
+
# 2026-08-19 on a freshly regenerated maintainer projection: 110 rules
|
|
21
|
+
# and 290 skills delivered twice, 203,873 tok of standing rule prose
|
|
22
|
+
# against a 110,000 cap. See ADR-236 and
|
|
23
|
+
# agents/evidence/analysis/single-delivery-partition-census.md.
|
|
16
24
|
# DRIFT — install at the other scope, DIFFERENT version (or unreadable).
|
|
17
25
|
# Drift will produce the 2026-05-25 failure mode (stale frontmatter
|
|
18
26
|
# registered alongside fresh frontmatter).
|
|
19
27
|
#
|
|
20
28
|
# Output shape (line-oriented, parseable by install.sh):
|
|
21
|
-
# <verdict>\t<tool-id>\t<other-scope-path>\t<other-version>\t<this-version>
|
|
29
|
+
# <verdict>\t<tool-id>\t<other-scope-path>\t<other-version>\t<this-version>[\t<overlap>]
|
|
30
|
+
#
|
|
31
|
+
# Field 6 (<overlap>) is emitted on WARN lines only: the number of entry names
|
|
32
|
+
# present in BOTH scope directories — what a same-version duplicate registration
|
|
33
|
+
# actually delivers twice. It is APPENDED rather than inserted, so every existing
|
|
34
|
+
# field-indexed consumer keeps working unchanged. `-1` means the count could not
|
|
35
|
+
# be taken — the scope being installed to does not exist yet, or a directory is
|
|
36
|
+
# unreadable — which is reported rather than silently rendered as 0. See
|
|
37
|
+
# `count_overlap` for why the two are different facts.
|
|
22
38
|
#
|
|
23
39
|
# A final summary line is emitted:
|
|
24
40
|
# SUMMARY\t<verdict>\t<count-OK>\t<count-WARN>\t<count-DRIFT>
|
|
@@ -67,6 +83,44 @@ dir_nonempty() {
|
|
|
67
83
|
[[ -d "$1" ]] && [[ -n "$(ls -A "$1" 2>/dev/null | head -1)" ]]
|
|
68
84
|
}
|
|
69
85
|
|
|
86
|
+
# Count entry names present in BOTH scope directories — what a same-version
|
|
87
|
+
# duplicate registration actually delivers twice, which is the cost the WARN
|
|
88
|
+
# verdict used to omit.
|
|
89
|
+
#
|
|
90
|
+
# Returns -1 rather than 0 when the count cannot be taken, because the two are
|
|
91
|
+
# different facts and reporting "0 overlap" for "could not look" is how a gate
|
|
92
|
+
# starts reading as coverage.
|
|
93
|
+
#
|
|
94
|
+
# R2 review corrected the docstring twice over. The copilot probe was named here
|
|
95
|
+
# as the concrete -1 case; it is UNREACHABLE, because `dir_nonempty`'s `[[ -d ]]`
|
|
96
|
+
# test short-circuits a single-file path to OK before probe_tool ever reaches a
|
|
97
|
+
# version comparison. The reachable cases are: the scope being installed TO does
|
|
98
|
+
# not exist yet (a first install at this scope — genuinely no overlap to count,
|
|
99
|
+
# not a failure), and a directory that exists but cannot be read.
|
|
100
|
+
#
|
|
101
|
+
# The unreadable case used to return 0 — the precise failure this function's own
|
|
102
|
+
# docstring claimed to prevent, because `cd` failed inside the process
|
|
103
|
+
# substitution and `comm` compared two empty streams. It is now detected before
|
|
104
|
+
# the comparison.
|
|
105
|
+
count_overlap() {
|
|
106
|
+
local a="$1" b="$2" n
|
|
107
|
+
if [[ ! -d "$a" ]] || [[ ! -d "$b" ]]; then
|
|
108
|
+
printf '%s' '-1'
|
|
109
|
+
return 0
|
|
110
|
+
fi
|
|
111
|
+
# Readability is checked explicitly: `ls` on an unreadable directory fails,
|
|
112
|
+
# and inside `<(...)` that failure is invisible to the caller.
|
|
113
|
+
if ! ls -A "$a" >/dev/null 2>&1 || ! ls -A "$b" >/dev/null 2>&1; then
|
|
114
|
+
printf '%s' '-1'
|
|
115
|
+
return 0
|
|
116
|
+
fi
|
|
117
|
+
n="$(comm -12 \
|
|
118
|
+
<(ls -A "$a" 2>/dev/null | sort) \
|
|
119
|
+
<(ls -A "$b" 2>/dev/null | sort) \
|
|
120
|
+
2>/dev/null | wc -l | tr -d ' ')" || n=''
|
|
121
|
+
printf '%s' "${n:--1}"
|
|
122
|
+
}
|
|
123
|
+
|
|
70
124
|
# Per-tool probe definitions: tool-id, user-scope path, project-scope path.
|
|
71
125
|
# Project-scope path is relative to "$TARGET_DIR" (or "$HOME/<proj>" for user).
|
|
72
126
|
probe_tool() {
|
|
@@ -111,10 +165,38 @@ probe_tool() {
|
|
|
111
165
|
local this_ver
|
|
112
166
|
this_ver="$(this_version)"
|
|
113
167
|
|
|
168
|
+
# Field 6 is the duplicate-registration count, and it is printed on BOTH
|
|
169
|
+
# verdicts. It used to appear only on WARN, which required the two scopes to
|
|
170
|
+
# report the SAME version — and `installed_version_at` returns `unknown` for
|
|
171
|
+
# `~/.claude/…` because a host directory has no `package.json` beside it. So
|
|
172
|
+
# every real cross-scope install classified as DRIFT and the count never
|
|
173
|
+
# printed at all: the instrumentation was live and unreachable, which reads as
|
|
174
|
+
# coverage in a diff and is strictly worse than absent.
|
|
175
|
+
#
|
|
176
|
+
# Resolved 2026-08-20, road-to-single-delivery Phase 5, blocker
|
|
177
|
+
# `warn-path-unreachable-without-version-marker`, option (b). The council had
|
|
178
|
+
# already put the reasoning on record in the round that decided the partition:
|
|
179
|
+
# *"overlap reporting should not depend on version equality in the first
|
|
180
|
+
# place. Duplicate files can and should be reported whether versions match,
|
|
181
|
+
# drift, or are unknown."* A duplicate is a duplicate at any version pair.
|
|
182
|
+
#
|
|
183
|
+
# (a) — having the installer stamp a version beside every host layer — was the
|
|
184
|
+
# larger change and is NOT what shipped: it alters a consumer-facing surface to
|
|
185
|
+
# repair a reporting line. (c) — treating `unknown` as "same version" for the
|
|
186
|
+
# overlap half — was rejected because it weakens the DRIFT block itself.
|
|
187
|
+
#
|
|
188
|
+
# Wire compatibility: the DRIFT line gains a SIXTH field. The only structured
|
|
189
|
+
# consumer (`src/server/routes/wizard.ts`) destructures the first five and
|
|
190
|
+
# ignores the rest, so this is additive.
|
|
191
|
+
local overlap
|
|
192
|
+
overlap="$(count_overlap "$other_path" "$this_scope_path")"
|
|
114
193
|
if [[ "$other_ver" == "unknown" ]] || [[ "$this_ver" == "unknown" ]] || [[ "$other_ver" != "$this_ver" ]]; then
|
|
115
|
-
printf 'DRIFT\t%s\t%s\t%s\t%s\n' "$tool" "$other_path" "$other_ver" "$this_ver"
|
|
194
|
+
printf 'DRIFT\t%s\t%s\t%s\t%s\t%s\n' "$tool" "$other_path" "$other_ver" "$this_ver" "$overlap"
|
|
116
195
|
else
|
|
117
|
-
|
|
196
|
+
# Same version, so no drift — but the duplicate registration is delivered
|
|
197
|
+
# twice, and field 6 is what makes that cost visible at the moment of the
|
|
198
|
+
# decision instead of only in a token census nobody runs.
|
|
199
|
+
printf 'WARN\t%s\t%s\t%s\t%s\t%s\n' "$tool" "$other_path" "$other_ver" "$this_ver" "$overlap"
|
|
118
200
|
fi
|
|
119
201
|
}
|
|
120
202
|
|
|
@@ -338,6 +338,25 @@ const COMPLAINT_PATTERNS: readonly RegExp[] = [
|
|
|
338
338
|
/\byou (didn'?t|did not|failed to|ignored|forgot)\b/i,
|
|
339
339
|
/\bthat'?s (wrong|not right|incorrect)\b/i,
|
|
340
340
|
/\byou (worked|did (it|that)) (wrong|incorrectly)\b/i,
|
|
341
|
+
// Recurrence phrasings. A restated complaint often carries none of the fault
|
|
342
|
+
// words above — "das habe ich dir schon dreimal gesagt" names no fault at
|
|
343
|
+
// all — so without these the SECOND and THIRD report of a defect were
|
|
344
|
+
// silently not records, which is exactly when the occurrences counter
|
|
345
|
+
// carries the most signal.
|
|
346
|
+
//
|
|
347
|
+
// Every branch requires an explicit COUNT word next to a saying-verb, and
|
|
348
|
+
// that is the precision knob: "nochmal" / "wieder" / "again" are deliberately
|
|
349
|
+
// NOT counts, because a bare repetition word fires on a large share of
|
|
350
|
+
// ordinary prompts ("mach das nochmal") and a spurious record becomes a
|
|
351
|
+
// spurious PR. Ordinals and numerals only.
|
|
352
|
+
/\b(schon|bereits)[^.!?]{0,30}(zum (zweiten|dritten|vierten|fünften) mal|(zwei|drei|vier|fünf)mal|mehrmals|mehrfach|\d+ ?mal)[^.!?]{0,20}(gesagt|erwähnt|geschrieben|angesprochen)\b/i,
|
|
353
|
+
/\b(sage|sag|sagte)[^.!?]{0,30}(zum (zweiten|dritten|vierten|fünften) mal|(zwei|drei|vier|fünf)mal|mehrmals|mehrfach|\d+ ?mal)\b/i,
|
|
354
|
+
/\bwie oft (soll|muss|müssen)\b/i,
|
|
355
|
+
// Typographic AND ASCII apostrophe: the curly form is what a real prompt
|
|
356
|
+
// carries, and matching only the ASCII one is a dead branch on live input.
|
|
357
|
+
/\bi ?(have|['’]ve)? ?already (told|said)\b/i,
|
|
358
|
+
/\b(told|said) (you )?(this|that|it)? ?(\w+|\d+) times?\b/i,
|
|
359
|
+
/\bhow many times (do|must|should) i\b/i,
|
|
341
360
|
];
|
|
342
361
|
|
|
343
362
|
// Phrasings that MATCH a complaint pattern but are not complaints — a curious
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Class-B path (road-to-org-telemetry Phase 5, step 5.2).
|
|
3
|
+
*
|
|
4
|
+
* Class A is the automatic half: structural fields, no content, shipped under
|
|
5
|
+
* recorded org consent. Class B is an abstracted CASE — what was expected
|
|
6
|
+
* versus what happened, and which artefacts were involved — and it ships only
|
|
7
|
+
* on explicit per-case approval of the exact text that would leave.
|
|
8
|
+
*
|
|
9
|
+
* THE GATE IS THE HUMAN READING THE OUTBOUND TEXT, NOT A FILTER. Rank 1 of the
|
|
10
|
+
* roadmap risk register is "an abstraction miss ships a path or an identifier
|
|
11
|
+
* out of a consumer project", and its stated mitigation is per-case review of
|
|
12
|
+
* the exact outbound text rather than a filter heuristic. So this module's job
|
|
13
|
+
* is to make the exact bytes visible and then refuse to release them unless the
|
|
14
|
+
* approval names those same bytes. `approve` takes a digest of the rendered
|
|
15
|
+
* text; a text that changed after the human read it no longer matches, and the
|
|
16
|
+
* release refuses rather than shipping something nobody approved.
|
|
17
|
+
*
|
|
18
|
+
* SILENT SHIPPING IS PERMANENTLY OUT OF SCOPE, and the step says so in those
|
|
19
|
+
* words. There is deliberately no "auto-approve", no allow-list of
|
|
20
|
+
* self-evidently safe classes, and no timeout that approves by default.
|
|
21
|
+
*
|
|
22
|
+
* TWO EXISTING PRIMITIVES ARE REUSED RATHER THAN RE-INVENTED:
|
|
23
|
+
* - `renderReport` is "the existing symptom format" the step names.
|
|
24
|
+
* - `egressBlockedReason` is the audited privacy floor — the same one the
|
|
25
|
+
* local corpus write and the upstream release already pass through. It
|
|
26
|
+
* REFUSES rather than rewriting, which is the property that makes it a gate;
|
|
27
|
+
* a module that silently scrubbed and shipped would be a soft gate wearing
|
|
28
|
+
* a hard gate's name.
|
|
29
|
+
*
|
|
30
|
+
* WHAT THIS MODULE DOES NOT DO: send anything. Transport and sink-side storage
|
|
31
|
+
* are Phase 5 step 5.3, transferred with `sink-choice` — no sink exists for an
|
|
32
|
+
* approved case to travel to. `ClassBRecord` is therefore built, gated and
|
|
33
|
+
* held; the field that carries the text is `case_text` and it is a typed
|
|
34
|
+
* member of a typed record, never a string interpolated into a prompt.
|
|
35
|
+
*/
|
|
36
|
+
import * as crypto from 'node:crypto';
|
|
37
|
+
|
|
38
|
+
import {
|
|
39
|
+
type DefectRecord,
|
|
40
|
+
egressBlockedReason,
|
|
41
|
+
renderReport,
|
|
42
|
+
} from './self_repair.js';
|
|
43
|
+
|
|
44
|
+
/** Schema version for the Class-B envelope, independent of Class A. */
|
|
45
|
+
export const CLASS_B_SCHEMA_VERSION = 1;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* One approved case.
|
|
49
|
+
*
|
|
50
|
+
* `case_text` is the ONLY free-form member and it exists on purpose: Class B
|
|
51
|
+
* is defined as an abstracted case, so a Class-B record without text would be
|
|
52
|
+
* a Class-A record. The controls are that it is (a) a named, typed field
|
|
53
|
+
* rather than an open `payload`, (b) present only after `approve` matched a
|
|
54
|
+
* digest a human read, and (c) documented as data end to end — see
|
|
55
|
+
* `assertNeverInterpolated`.
|
|
56
|
+
*/
|
|
57
|
+
export interface ClassBRecord {
|
|
58
|
+
schema_version: number;
|
|
59
|
+
record_class: 'case';
|
|
60
|
+
/** Detector / intake class, from the pinned vocabulary. */
|
|
61
|
+
defect_class: string;
|
|
62
|
+
/** Fingerprint of the local record this case abstracts. */
|
|
63
|
+
fingerprint: string;
|
|
64
|
+
/** SHA-256 of `case_text`, so a consumer can verify what was approved. */
|
|
65
|
+
case_digest: string;
|
|
66
|
+
/** The abstracted case, exactly as the human approved it. */
|
|
67
|
+
case_text: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type ClassBRefusal =
|
|
71
|
+
| { ok: false; reason: string; kind: 'privacy-floor' | 'digest-mismatch' | 'empty' };
|
|
72
|
+
|
|
73
|
+
export type ClassBResult = { ok: true; record: ClassBRecord } | ClassBRefusal;
|
|
74
|
+
|
|
75
|
+
/** SHA-256 hex of the exact bytes a human is asked to approve. */
|
|
76
|
+
export function caseDigest(text: string): string {
|
|
77
|
+
return crypto.createHash('sha256').update(text, 'utf-8').digest('hex');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Render the exact outbound text for one record, or refuse.
|
|
82
|
+
*
|
|
83
|
+
* Refusal comes from the audited privacy floor and is returned as a reason
|
|
84
|
+
* string for the caller to show. It is NOT downgraded to a scrub-and-continue:
|
|
85
|
+
* a record whose evidence trips the floor stays local, which is the same
|
|
86
|
+
* verdict the upstream release path already reaches.
|
|
87
|
+
*/
|
|
88
|
+
export function renderCase(
|
|
89
|
+
record: DefectRecord,
|
|
90
|
+
repoRoot?: string | null,
|
|
91
|
+
): { ok: true; text: string; digest: string } | ClassBRefusal {
|
|
92
|
+
const blocked = egressBlockedReason(record, repoRoot ?? null);
|
|
93
|
+
if (blocked !== null) {
|
|
94
|
+
return { ok: false, reason: blocked, kind: 'privacy-floor' };
|
|
95
|
+
}
|
|
96
|
+
// `issue` is the route whose rendering carries no patch and no repository
|
|
97
|
+
// path — the narrowest of the existing shapes, and the right one for a
|
|
98
|
+
// case that travels to a telemetry sink rather than to a code host.
|
|
99
|
+
const text = renderReport(record, 'issue');
|
|
100
|
+
if (text.trim() === '') {
|
|
101
|
+
return { ok: false, reason: 'nothing to send', kind: 'empty' };
|
|
102
|
+
}
|
|
103
|
+
return { ok: true, text, digest: caseDigest(text) };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Approve one rendered case for release.
|
|
108
|
+
*
|
|
109
|
+
* `approved_digest` is what the human saw. If the text has changed since —
|
|
110
|
+
* a new occurrence folded in, a re-render, anything — the digests differ and
|
|
111
|
+
* this refuses. That is the difference between approving a case and approving
|
|
112
|
+
* a category.
|
|
113
|
+
*/
|
|
114
|
+
export function approve(
|
|
115
|
+
record: DefectRecord,
|
|
116
|
+
text: string,
|
|
117
|
+
approved_digest: string,
|
|
118
|
+
): ClassBResult {
|
|
119
|
+
const actual = caseDigest(text);
|
|
120
|
+
if (actual !== approved_digest) {
|
|
121
|
+
return {
|
|
122
|
+
ok: false,
|
|
123
|
+
kind: 'digest-mismatch',
|
|
124
|
+
reason:
|
|
125
|
+
'the approved text is not the text that would be sent — '
|
|
126
|
+
+ 're-render, read it again, and approve the new digest',
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
ok: true,
|
|
131
|
+
record: {
|
|
132
|
+
schema_version: CLASS_B_SCHEMA_VERSION,
|
|
133
|
+
record_class: 'case',
|
|
134
|
+
defect_class: record.defect_class,
|
|
135
|
+
fingerprint: record.fingerprint,
|
|
136
|
+
case_digest: actual,
|
|
137
|
+
case_text: text,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Serialise an approved case as ONE JSON object.
|
|
144
|
+
*
|
|
145
|
+
* This is the whole of "quoted, typed data" (step 5.3's repository-side half):
|
|
146
|
+
* the text is a JSON string value, so a downstream reader parses it into a
|
|
147
|
+
* field. Nothing here concatenates it into a sentence, a prompt, or a shell
|
|
148
|
+
* argument, and rank 2 of the risk register — a crafted complaint steering a
|
|
149
|
+
* generated change — is what that discipline is for.
|
|
150
|
+
*/
|
|
151
|
+
export function serialiseCase(record: ClassBRecord): string {
|
|
152
|
+
return JSON.stringify(record);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Guard for a downstream consumer: refuse a prompt-shaped use of case text.
|
|
157
|
+
*
|
|
158
|
+
* Deliberately a narrow, honest tool. It cannot stop a caller that never asks;
|
|
159
|
+
* what it does is give the generation step in Phase 6 something to call, so
|
|
160
|
+
* "reads taxonomy fields only, never Class-B free text" is a check rather than
|
|
161
|
+
* a convention. The intended use is at the boundary where a record becomes
|
|
162
|
+
* model input.
|
|
163
|
+
*/
|
|
164
|
+
export function assertNeverInterpolated(candidate: string, record: ClassBRecord): void {
|
|
165
|
+
if (record.case_text !== '' && candidate.includes(record.case_text)) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
'Class-B case text must reach a model as quoted data, never as part '
|
|
168
|
+
+ 'of an instruction string — pass the record, not a concatenation',
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -120,6 +120,76 @@ function isShortLine(value: unknown, max: number): value is string {
|
|
|
120
120
|
return typeof value === 'string' && value.length > 0 && value.length <= max && !value.includes('\n');
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Is this a path ref rather than prose?
|
|
125
|
+
*
|
|
126
|
+
* The discriminator is **whitespace**, and it is deliberately the only one.
|
|
127
|
+
* A path ref is one token; a prose sentence is several words. Anything
|
|
128
|
+
* narrower — a character class, a required extension, a mandatory `/` —
|
|
129
|
+
* rejects entries this tree really uses: a bare directory (`docs`), a sibling
|
|
130
|
+
* worktree (`../other-worktree/`), a dotfile. Anything wider stops telling the
|
|
131
|
+
* two shapes apart at all.
|
|
132
|
+
*
|
|
133
|
+
* This is a SHAPE check, never an existence or semantics check. A single-token
|
|
134
|
+
* value that is not a real path passes and then simply never matches anything,
|
|
135
|
+
* which is correct: the failure this closes is a field whose own documentation
|
|
136
|
+
* says "a list of path refs" silently accepting full sentences.
|
|
137
|
+
*
|
|
138
|
+
* **It is not a matcher, and a consumer must not mistake it for one.** It
|
|
139
|
+
* answers "is this one token", never "does this entry cover that write target".
|
|
140
|
+
* It admits a bare directory, a trailing-slash relative ref, a glob and a
|
|
141
|
+
* `file:line` ref; normalisation, directory-prefix matching and glob matching
|
|
142
|
+
* are deliberately unspecified here, because they are a separate decision that
|
|
143
|
+
* belongs to whatever ships the comparison. Reuse this for what VALIDATES;
|
|
144
|
+
* decide separately what MATCHES.
|
|
145
|
+
*
|
|
146
|
+
* **Stated default, not a measured optimum:** zero tracked paths in this repo
|
|
147
|
+
* contain whitespace, so the rule costs nothing here. *Revisit-if* a consumer
|
|
148
|
+
* legitimately needs a path containing a space — that needs a quoting
|
|
149
|
+
* convention, not a wider character class.
|
|
150
|
+
*/
|
|
151
|
+
export function isPathRef(value: unknown): value is string {
|
|
152
|
+
return typeof value === 'string' && value.length > 0 && !/\s/.test(value);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* `checkList` plus the path-ref shape, for a field whose own documentation
|
|
157
|
+
* calls its entries path refs. Names the offending entries: "one of your
|
|
158
|
+
* twelve entries is wrong" is not an actionable error.
|
|
159
|
+
*
|
|
160
|
+
* The shape report is suppressed for exactly one reason — the same entries
|
|
161
|
+
* already failed the per-entry budget, where reporting both says the same thing
|
|
162
|
+
* twice. It is deliberately NOT suppressed by the entry-COUNT error, which is
|
|
163
|
+
* orthogonal: a 41-entry list of prose sentences must report both problems in
|
|
164
|
+
* one pass, or the author trims to 40, re-validates, and only then learns the
|
|
165
|
+
* entries were prose — an extra refuse-and-repair round-trip on the write path
|
|
166
|
+
* this contract points at as the repair mechanism.
|
|
167
|
+
*/
|
|
168
|
+
function checkPathRefList(
|
|
169
|
+
errors: string[],
|
|
170
|
+
field: string,
|
|
171
|
+
value: unknown,
|
|
172
|
+
max: number,
|
|
173
|
+
required: boolean,
|
|
174
|
+
): void {
|
|
175
|
+
checkList(errors, field, value, max, required);
|
|
176
|
+
if (!Array.isArray(value)) return;
|
|
177
|
+
// Same-entry duplicate only: an entry that failed the budget is already
|
|
178
|
+
// reported, and its shape adds nothing a reader can act on separately.
|
|
179
|
+
if (value.some((x) => !isShortLine(x, max))) return;
|
|
180
|
+
|
|
181
|
+
const bad = value.filter((x) => !isPathRef(x));
|
|
182
|
+
if (bad.length === 0) return;
|
|
183
|
+
|
|
184
|
+
const shown = bad.slice(0, 3).map((x) => JSON.stringify(String(x))).join(', ');
|
|
185
|
+
errors.push(
|
|
186
|
+
`${field} carries ${bad.length} entr${bad.length === 1 ? 'y' : 'ies'} that are prose, not path refs: ` +
|
|
187
|
+
`${shown}${bad.length > 3 ? ', …' : ''} — write the path (src/generated/api.ts, ../other-worktree/) ` +
|
|
188
|
+
'or drop the entry; a sentence naming a file cannot be compared against a write target, and being ' +
|
|
189
|
+
'comparable is the whole reason this is a field rather than a line of prose',
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
123
193
|
function checkList(
|
|
124
194
|
errors: string[],
|
|
125
195
|
field: string,
|
|
@@ -257,6 +327,14 @@ export interface MainSessionRecycleEnvelope {
|
|
|
257
327
|
* adjudication set (a field whose presence is checkable survives where a
|
|
258
328
|
* doctrine whose effect is unmeasured did not).
|
|
259
329
|
*
|
|
330
|
+
* The path-ref shape is **enforced** (`isPathRef`), not merely documented.
|
|
331
|
+
* It was documented-only until 2026-08-19, and the gap was not academic:
|
|
332
|
+
* the validator applied the ref CHARACTER BUDGET and no shape rule, so the
|
|
333
|
+
* one real non-empty producer — a composing model — wrote prose sentences
|
|
334
|
+
* naming files, and every one of them validated. A field that accepts
|
|
335
|
+
* whatever prose the writer felt like is `constraints` with extra steps,
|
|
336
|
+
* and nothing downstream can compare it to a write target.
|
|
337
|
+
*
|
|
260
338
|
* Optional: an envelope with nothing off limits states nothing here, and an
|
|
261
339
|
* empty list is not a claim that everything is writable.
|
|
262
340
|
*/
|
|
@@ -596,7 +674,9 @@ export function validateRecycleEnvelope(input: unknown): string[] {
|
|
|
596
674
|
errors.push(...decisionTagErrors(e['decisions']));
|
|
597
675
|
checkList(errors, 'constraints', e['constraints'], MAX_LINE_CHARS, false);
|
|
598
676
|
// Path refs, so the REF budget applies — not the prose one `constraints` uses.
|
|
599
|
-
|
|
677
|
+
// And the SHAPE is checked, not only the budget: the budget alone accepted a
|
|
678
|
+
// prose sentence naming a file, which is what the only real producer wrote.
|
|
679
|
+
checkPathRefList(errors, 'do_not_touch', e['do_not_touch'], MAX_REF_CHARS, false);
|
|
600
680
|
checkList(errors, 'open_worker_envelopes', e['open_worker_envelopes'], MAX_REF_CHARS, false);
|
|
601
681
|
checkList(errors, 'artifact_paths', e['artifact_paths'], MAX_REF_CHARS, false);
|
|
602
682
|
checkList(errors, 'suggested_skills', e['suggested_skills'], MAX_REF_CHARS, false);
|
|
@@ -23,7 +23,17 @@ const NAMED = /^ADR-(\d{3})-([a-z0-9-]+)\.md$/;
|
|
|
23
23
|
const FM = /^---\n([\s\S]*?)\n---/;
|
|
24
24
|
// ^([a-z_]+):\s*(.+?)\s*$ (MULTILINE)
|
|
25
25
|
const FIELD = /^([a-z_]+):[ \t]*(.+?)[ \t]*$/gm;
|
|
26
|
-
|
|
26
|
+
/**
|
|
27
|
+
* The index used to render `| # | Title | Status | Date | Supersedes |`, which
|
|
28
|
+
* is forward-only: a dead ADR showed `superseded | —` and the reader learned it
|
|
29
|
+
* was dead but not by what. `superseded_by` was read nowhere despite being a
|
|
30
|
+
* documented field, and `amended_by` had no representation at all — 18 ADRs
|
|
31
|
+
* carry an amendment block and the index could not distinguish one with five
|
|
32
|
+
* amendments from one never touched.
|
|
33
|
+
*/
|
|
34
|
+
const HEAD =
|
|
35
|
+
'| # | Title | Status | Date | Supersedes | Superseded by | Amended by |\n' +
|
|
36
|
+
'|---|---|---|---|---|---|---|';
|
|
27
37
|
|
|
28
38
|
type Meta = Record<string, string>;
|
|
29
39
|
interface Row extends Meta {
|
|
@@ -182,7 +192,8 @@ export function row(r: Row): string {
|
|
|
182
192
|
const label = 'num' in r ? `ADR-${r.num}` : (r.path as string).slice(0, -3);
|
|
183
193
|
return (
|
|
184
194
|
`| [${label}](${r.path}) | ${title} | ${r.status ?? '—'} ` +
|
|
185
|
-
`| ${r.date ?? '—'} | ${r.supersedes ?? '—'}
|
|
195
|
+
`| ${r.date ?? '—'} | ${r.supersedes ?? '—'} | ${r.superseded_by ?? '—'} ` +
|
|
196
|
+
`| ${r.amended_by ?? '—'} |`
|
|
186
197
|
);
|
|
187
198
|
}
|
|
188
199
|
|