@event4u/agent-config 14.19.0 → 14.21.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 +351 -156
- package/README.md +1 -1
- package/agents/templates/.ai-council.yml.example +10 -0
- package/dist/agent-src/commands/agent-handoff.md +42 -41
- package/dist/agent-src/commands/analyze/inbox.md +1 -1
- package/dist/agent-src/commands/analyze/{reference-repo.md → repo.md} +247 -32
- package/dist/agent-src/commands/analyze/roadmap-repos.md +129 -0
- package/dist/agent-src/commands/analyze.md +12 -7
- package/dist/agent-src/commands/feature/plan.md +55 -16
- package/dist/agent-src/commands/feature/refactor.md +8 -3
- package/dist/agent-src/commands/humanize.md +48 -4
- package/dist/agent-src/contexts/execution/contract-decision-sheet.md +22 -1
- package/dist/agent-src/contexts/execution/host-capability-manifest.md +11 -2
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +35 -2
- package/dist/agent-src/contexts/execution/terminal-states.md +4 -1
- package/dist/agent-src/rules/evaluator-independence.md +4 -4
- package/dist/agent-src/rules/external-reference-deep-dive.md +2 -2
- package/dist/agent-src/rules/untrusted-input-defense.md +0 -2
- package/dist/agent-src/skills/competitive-positioning/SKILL.md +2 -2
- package/dist/agent-src/skills/evaluate-llm-feature/SKILL.md +1 -1
- package/dist/agent-src/skills/humanizer/references/anti-aiisms.md +27 -11
- package/dist/agent-src/skills/learning-to-rule-or-skill/SKILL.md +1 -1
- package/dist/agent-src/skills/markitdown/SKILL.md +2 -2
- package/dist/agent-src/skills/repomix-packer/SKILL.md +1 -1
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +28 -6
- package/dist/agent-src/skills/technical-specification/SKILL.md +4 -0
- package/dist/agent-src/skills/test-driven-development/SKILL.md +1 -3
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/features.md +5 -0
- package/dist/agent-src/templates/roadmaps.md +29 -1
- package/dist/agent-src/templates/scripts/telemetry/mcp_lite.ts +213 -0
- package/dist/agent-src/templates/scripts/telemetry/report_renderer.ts +16 -0
- package/dist/agent-src/templates/scripts/telemetry_report.ts +22 -2
- package/dist/cli/mcp/dispatch.js +10 -0
- package/dist/cli/mcp/stdio.js +33 -2
- package/dist/cli/mcp/telemetry.js +227 -0
- package/dist/cli-delegate/{chunk-KPBWFOPZ.js → chunk-2FTMENJL.js} +194 -37
- package/dist/cli-delegate/{chunk-PMEKGDED.js → chunk-2TAQ2K4V.js} +166 -1
- package/dist/cli-delegate/{chunk-GZTMRYCN.js → chunk-643Z7FIC.js} +79 -12
- package/dist/cli-delegate/{chunk-CYH5SP6M.js → chunk-I6RHFQEG.js} +151 -16
- package/dist/cli-delegate/{chunk-IDHUQIXP.js → chunk-J3RFNKG4.js} +41 -5
- package/dist/cli-delegate/{chunk-7PRE6ZV2.js → chunk-YS6XTLEK.js} +199 -4
- package/dist/cli-delegate/chunk-ZVBOHSVF.js +1109 -0
- package/dist/cli-delegate/cmd_conformance.js +88 -110
- package/dist/cli-delegate/cmd_doctor.js +3 -3
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +42 -12
- 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 +59 -902
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- 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 +62 -31
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +7 -6
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +6 -5
- package/dist/hooks/dispatch.js +3801 -2803
- package/dist/install/install.mjs +396 -168
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +809 -180
- package/dist/scripts/ai_council/content_ceiling.js +63 -0
- package/dist/scripts/ai_council/seat_policy.js +106 -0
- package/dist/scripts/ai_council/transport_resolver.js +21 -0
- package/dist/scripts/mcp_server/telemetry.js +148 -0
- package/docs/guidelines/agent-infra/naming.md +1 -1
- package/docs/guidelines/agent-infra/security-lint-containment.md +46 -6
- package/docs/guidelines/agent-infra/self-improvement-pipeline.md +4 -4
- package/docs/guidelines/agent-infra/untrusted-input-spotlighting.md +1 -1
- package/docs/guidelines/redundancy-taxonomy.md +28 -0
- package/llms.txt +14 -14
- package/package.json +1 -1
- package/src/agent-src/templates/scripts/telemetry/mcp_lite.ts +213 -0
- package/src/agent-src/templates/scripts/telemetry/report_renderer.ts +16 -0
- package/src/agent-src/templates/scripts/telemetry_report.ts +22 -2
- package/src/config/assurance-capability-registry.json +9 -0
- package/src/config/gate-coverage.yml +55 -0
- package/src/config/gate-violation-baselines.json +18 -6
- package/src/config/release-gate-locality.yml +4 -3
- package/src/scripts/_cli/cmd_session_recycle.ts +51 -3
- package/src/scripts/_lib/audit_field_provenance.ts +44 -3
- package/src/scripts/_lib/blocked_by_marker.ts +189 -0
- package/src/scripts/_lib/host_capability.ts +76 -1
- package/src/scripts/_lib/install_reach_checks.ts +33 -1
- package/src/scripts/_lib/mcp_bridge.ts +222 -10
- package/src/scripts/_lib/mcp_consent_residual.ts +173 -0
- package/src/scripts/_lib/orchestration_record.ts +23 -1
- package/src/scripts/_lib/recycle_envelope_paths.ts +190 -0
- package/src/scripts/_lib/release_highlights.ts +19 -211
- package/src/scripts/_lib/release_material.ts +27 -77
- package/src/scripts/_lib/release_position.ts +96 -0
- package/src/scripts/_lib/review_skipped_record.ts +17 -1
- package/src/scripts/_lib/security_lint.ts +179 -7
- package/src/scripts/_lib/self_repair.ts +141 -6
- package/src/scripts/_lib/self_repair_store.ts +273 -7
- package/src/scripts/_lib/structured_ask.ts +117 -0
- package/src/scripts/_lib/subagent_capsule.ts +155 -3
- package/src/scripts/_lib/tty_prompt.ts +76 -0
- package/src/scripts/ai_council/api_transport.ts +163 -0
- package/src/scripts/ai_council/clients.ts +58 -63
- package/src/scripts/ai_council/config.ts +13 -0
- package/src/scripts/ai_council/content_ceiling.ts +79 -0
- package/src/scripts/ai_council/jury_aggregate.ts +178 -0
- package/src/scripts/ai_council/seat_policy.ts +137 -0
- package/src/scripts/ai_council/status_surface.ts +79 -0
- package/src/scripts/ai_council/transport_resolver.ts +59 -1
- package/src/scripts/ai_tells_rules.ts +446 -5
- package/src/scripts/ask_block_census.ts +499 -0
- package/src/scripts/bench_humanizer_eval.ts +264 -40
- package/src/scripts/bench_prose_tells_fp.ts +198 -0
- package/src/scripts/build_rule_trigger_matrix.ts +5 -0
- package/src/scripts/check_branch_work_committed.ts +234 -0
- package/src/scripts/check_mcp_doc_drift.ts +217 -0
- package/src/scripts/check_pack_size.ts +321 -3
- package/src/scripts/check_release_highlights.ts +0 -157
- package/src/scripts/council_cli.ts +18 -27
- package/src/scripts/detect_ai_tells.ts +142 -20
- package/src/scripts/handoff_context_hook.ts +54 -7
- package/src/scripts/harvest_reference_tokens.ts +698 -0
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +31 -1
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +45 -1
- package/src/scripts/hooks/one_question_per_ask_hook.ts +191 -0
- package/src/scripts/hooks/session_eol_hook.ts +18 -7
- package/src/scripts/install-hooks.sh +52 -0
- package/src/scripts/install.ts +4 -4
- package/src/scripts/lint_agent_security.ts +316 -25
- package/src/scripts/lint_confusables.ts +42 -6
- package/src/scripts/lint_handoffs.ts +0 -132
- package/src/scripts/lint_hidden_unicode.ts +49 -7
- package/src/scripts/lint_instruction_smuggling.ts +42 -6
- package/src/scripts/lint_mcp_config_security.ts +42 -6
- package/src/scripts/lint_roadmap_blockers.ts +51 -1
- package/src/scripts/lint_roadmap_later_disposition.ts +217 -27
- package/src/scripts/lint_skill_frontmatter_safety.ts +43 -7
- package/src/scripts/mcp_server/resources.ts +41 -0
- package/src/scripts/mcp_server/telemetry.ts +19 -4
- package/src/scripts/migrate_command_suggestions.ts +1 -1
- package/src/scripts/orchestration_record.ts +12 -1
- package/src/scripts/probe_unblocked_ask.ts +135 -2
- package/src/scripts/release.ts +50 -95
- package/src/scripts/release_drill.ts +6 -10
- package/src/scripts/release_env.ts +1 -0
- package/src/scripts/release_publication.ts +40 -31
- package/src/scripts/self_repair_cli.ts +31 -1
- package/src/scripts/self_review_gate.ts +409 -15
- package/src/scripts/skill_scout.ts +69 -0
- package/dist/cli-delegate/chunk-2GNWGMAL.js +0 -172
- package/dist/cli-delegate/chunk-LPPWO5CE.js +0 -161
|
@@ -30,11 +30,15 @@
|
|
|
30
30
|
*/
|
|
31
31
|
|
|
32
32
|
import { readFileSync } from "node:fs";
|
|
33
|
+
import type { MatchResult, RawOccurrence } from "./ai_tells_rules.js";
|
|
33
34
|
import {
|
|
34
35
|
ALL_TELL_RULES,
|
|
35
36
|
DEFAULT_MAX_CLUSTER_SCORE,
|
|
36
37
|
DEFAULT_MAX_DASH_DENSITY,
|
|
37
38
|
DEFAULT_MAX_HARD,
|
|
39
|
+
MIN_CONSISTENT_OCCURRENCES,
|
|
40
|
+
MIN_CONSISTENT_SPREAD,
|
|
41
|
+
MIN_DENSITY_WORDS,
|
|
38
42
|
sniffLanguage,
|
|
39
43
|
} from "./ai_tells_rules.js";
|
|
40
44
|
import { _classify } from "./lint_hidden_unicode.js";
|
|
@@ -48,6 +52,15 @@ import { _classify } from "./lint_hidden_unicode.js";
|
|
|
48
52
|
*/
|
|
49
53
|
export const MAX_SCAN_CHARS = 100_000;
|
|
50
54
|
|
|
55
|
+
/** Where one match sits, so a finding can be located rather than only counted. */
|
|
56
|
+
export interface Occurrence {
|
|
57
|
+
/** 1-based. */
|
|
58
|
+
line: number;
|
|
59
|
+
/** 1-based, in characters. */
|
|
60
|
+
column: number;
|
|
61
|
+
text: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
51
64
|
export interface RuleHit {
|
|
52
65
|
id: string;
|
|
53
66
|
group: string;
|
|
@@ -55,6 +68,18 @@ export interface RuleHit {
|
|
|
55
68
|
count: number;
|
|
56
69
|
weight: number;
|
|
57
70
|
samples: string[];
|
|
71
|
+
/** Every match, located. `count` is `occurrences.length`. */
|
|
72
|
+
occurrences: Occurrence[];
|
|
73
|
+
/**
|
|
74
|
+
* `uniform` when the pattern is used at least
|
|
75
|
+
* `MIN_CONSISTENT_OCCURRENCES` times across at least
|
|
76
|
+
* `MIN_CONSISTENT_SPREAD` of the document: a habit the author carries
|
|
77
|
+
* through the whole piece, which is evidence of style rather than of N
|
|
78
|
+
* independent tells. A uniform cluster pattern contributes its weight ONCE.
|
|
79
|
+
*/
|
|
80
|
+
consistency: "uniform" | "scattered";
|
|
81
|
+
/** What the cluster score actually charged for this rule. */
|
|
82
|
+
scored_count: number;
|
|
58
83
|
}
|
|
59
84
|
|
|
60
85
|
/**
|
|
@@ -77,9 +102,16 @@ export interface TellReport {
|
|
|
77
102
|
cluster_hits: RuleHit[];
|
|
78
103
|
hard_total: number;
|
|
79
104
|
cluster_score: number;
|
|
80
|
-
|
|
105
|
+
/**
|
|
106
|
+
* `null` below `MIN_DENSITY_WORDS`: a per-500-words rate extrapolated from a
|
|
107
|
+
* handful of words is an artifact of the denominator, not a reading of the
|
|
108
|
+
* prose. `null` says "not evaluated" where a `0` would say "measured clean".
|
|
109
|
+
*/
|
|
110
|
+
cluster_score_per_500: number | null;
|
|
81
111
|
dash_count: number;
|
|
82
|
-
dash_density_per_500: number;
|
|
112
|
+
dash_density_per_500: number | null;
|
|
113
|
+
/** False when the text is under the floor and neither density was applied. */
|
|
114
|
+
density_evaluated: boolean;
|
|
83
115
|
per_pattern: Record<string, number>;
|
|
84
116
|
truncated: boolean;
|
|
85
117
|
hidden_unicode: HiddenUnicodeFinding[];
|
|
@@ -220,27 +252,82 @@ export function stripExempt(text: string): { base: string; forQuotes: string } {
|
|
|
220
252
|
const base = t
|
|
221
253
|
.replace(/"[^"\n]{1,300}"/g, " ")
|
|
222
254
|
.replace(/“[^”\n]{1,300}”/g, " ")
|
|
223
|
-
|
|
255
|
+
// A single-quoted SPAN, never a pair of contractions. The previous form
|
|
256
|
+
// matched any two apostrophes on a line with a space between them, so
|
|
257
|
+
// "In today's fast-paced world, let's be honest" was read as a quotation
|
|
258
|
+
// and stripped whole — which silently removed two tell families from the
|
|
259
|
+
// scan. An opening quote must start a token and a closing quote must end
|
|
260
|
+
// one; an apostrophe inside a word is never either.
|
|
261
|
+
.replace(
|
|
262
|
+
/(^|[\s([{"“])'([^'\n]{1,120})'(?=[\s)\]},.!?;:"”]|$)/g,
|
|
263
|
+
(m, lead: string, inner: string) => (inner.includes(" ") ? `${lead} ` : m),
|
|
264
|
+
);
|
|
224
265
|
return { base, forQuotes };
|
|
225
266
|
}
|
|
226
267
|
|
|
227
|
-
function countMatches(text: string, patterns: RegExp[]):
|
|
228
|
-
|
|
229
|
-
const samples: string[] = [];
|
|
268
|
+
function countMatches(text: string, patterns: RegExp[]): MatchResult {
|
|
269
|
+
const occurrences: RawOccurrence[] = [];
|
|
230
270
|
for (const p of patterns) {
|
|
231
271
|
const flags = p.flags.includes("g") ? p.flags : p.flags + "g";
|
|
232
272
|
const re = new RegExp(p.source, flags);
|
|
233
273
|
for (const m of text.matchAll(re)) {
|
|
234
|
-
|
|
235
|
-
if (samples.length < 3) samples.push(m[0].slice(0, 60).trim());
|
|
274
|
+
occurrences.push({ index: m.index ?? 0, text: m[0] });
|
|
236
275
|
}
|
|
237
276
|
}
|
|
238
|
-
|
|
277
|
+
occurrences.sort((a, b) => a.index - b.index);
|
|
278
|
+
return {
|
|
279
|
+
count: occurrences.length,
|
|
280
|
+
samples: occurrences.slice(0, 3).map((o) => o.text.slice(0, 60).trim()),
|
|
281
|
+
occurrences,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Offset → 1-based line and column, computed once per scan text. */
|
|
286
|
+
function lineIndex(text: string): (index: number) => { line: number; column: number } {
|
|
287
|
+
const starts = [0];
|
|
288
|
+
for (let i = 0; i < text.length; i += 1) if (text[i] === "\n") starts.push(i + 1);
|
|
289
|
+
return (index: number) => {
|
|
290
|
+
let lo = 0;
|
|
291
|
+
let hi = starts.length - 1;
|
|
292
|
+
while (lo < hi) {
|
|
293
|
+
const mid = (lo + hi + 1) >> 1;
|
|
294
|
+
if ((starts[mid] as number) <= index) lo = mid;
|
|
295
|
+
else hi = mid - 1;
|
|
296
|
+
}
|
|
297
|
+
return { line: lo + 1, column: index - (starts[lo] as number) + 1 };
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Is this pattern a habit the author carries through the document, or a local
|
|
303
|
+
* repetition? Without a captured voice sample the skill falls back to defaults,
|
|
304
|
+
* so consistency is the only intent signal available from the text itself.
|
|
305
|
+
*/
|
|
306
|
+
export function classifyConsistency(
|
|
307
|
+
occurrences: ReadonlyArray<RawOccurrence>,
|
|
308
|
+
textLength: number,
|
|
309
|
+
): "uniform" | "scattered" {
|
|
310
|
+
if (occurrences.length < MIN_CONSISTENT_OCCURRENCES) return "scattered";
|
|
311
|
+
const first = occurrences[0]?.index ?? 0;
|
|
312
|
+
const last = occurrences[occurrences.length - 1]?.index ?? 0;
|
|
313
|
+
const spread = (last - first) / Math.max(textLength, 1);
|
|
314
|
+
return spread >= MIN_CONSISTENT_SPREAD ? "uniform" : "scattered";
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export interface AnalyzeOptions {
|
|
318
|
+
/**
|
|
319
|
+
* Rule ids to leave out of the scan entirely — not merely out of the
|
|
320
|
+
* reported vector. Used by the paired bench to ask what a family was
|
|
321
|
+
* contributing: a metric that changes when the rule is removed is one the
|
|
322
|
+
* rule was carrying, and a metric that does not is one it was not.
|
|
323
|
+
*/
|
|
324
|
+
exclude?: ReadonlySet<string>;
|
|
239
325
|
}
|
|
240
326
|
|
|
241
327
|
export function analyzeText(
|
|
242
328
|
text: string,
|
|
243
329
|
languageOpt: "en" | "de" | "auto" = "auto",
|
|
330
|
+
options: AnalyzeOptions = {},
|
|
244
331
|
): TellReport {
|
|
245
332
|
// Scan the RAW text for hidden-instruction vectors before any stripping —
|
|
246
333
|
// the smuggling layer is invisible and must be reported (untrusted-input).
|
|
@@ -253,16 +340,29 @@ export function analyzeText(
|
|
|
253
340
|
const words = base.split(/\s+/).filter(Boolean).length || 1;
|
|
254
341
|
const language = languageOpt === "auto" ? sniffLanguage(base) : languageOpt;
|
|
255
342
|
|
|
343
|
+
const locateBase = lineIndex(base);
|
|
344
|
+
const locateQuotes = lineIndex(forQuotes);
|
|
256
345
|
const hardHits: RuleHit[] = [];
|
|
257
346
|
const clusterHits: RuleHit[] = [];
|
|
258
347
|
const perPattern: Record<string, number> = {};
|
|
259
348
|
|
|
260
349
|
for (const rule of ALL_TELL_RULES) {
|
|
261
350
|
if (rule.language !== "any" && rule.language !== language) continue;
|
|
351
|
+
if (options.exclude?.has(rule.id)) continue;
|
|
262
352
|
const scanText = rule.id === "tell-curly-quotes" ? forQuotes : base;
|
|
263
|
-
const
|
|
353
|
+
const result: MatchResult = rule.match
|
|
354
|
+
? rule.match(scanText, language)
|
|
355
|
+
: countMatches(scanText, rule.patterns);
|
|
356
|
+
const { count, samples, occurrences } = result;
|
|
264
357
|
if (count === 0) continue;
|
|
265
358
|
perPattern[rule.id] = count;
|
|
359
|
+
const locate = scanText === forQuotes ? locateQuotes : locateBase;
|
|
360
|
+
const consistency = classifyConsistency(occurrences, scanText.length);
|
|
361
|
+
// A uniform pattern is charged once. A HARD rule is never discounted: its
|
|
362
|
+
// members are chat artifacts and cutoff disclaimers, and repeating one
|
|
363
|
+
// through a document makes it more of a defect, not less of one.
|
|
364
|
+
const scoredCount =
|
|
365
|
+
rule.severity === "cluster" && consistency === "uniform" ? 1 : count;
|
|
266
366
|
const hit: RuleHit = {
|
|
267
367
|
id: rule.id,
|
|
268
368
|
group: rule.group,
|
|
@@ -270,14 +370,19 @@ export function analyzeText(
|
|
|
270
370
|
count,
|
|
271
371
|
weight: rule.weight,
|
|
272
372
|
samples,
|
|
373
|
+
occurrences: occurrences.map((o) => ({ ...locate(o.index), text: o.text.slice(0, 120) })),
|
|
374
|
+
consistency,
|
|
375
|
+
scored_count: scoredCount,
|
|
273
376
|
};
|
|
274
377
|
(rule.severity === "hard" ? hardHits : clusterHits).push(hit);
|
|
275
378
|
}
|
|
276
379
|
|
|
277
380
|
const hardTotal = hardHits.reduce((s, h) => s + h.count, 0);
|
|
278
|
-
const clusterScore = clusterHits.reduce((s, h) => s + h.
|
|
381
|
+
const clusterScore = clusterHits.reduce((s, h) => s + h.scored_count * h.weight, 0);
|
|
279
382
|
const dashCount = (forQuotes.match(/[—–]/g) ?? []).length;
|
|
280
|
-
const
|
|
383
|
+
const densityEvaluated = words >= MIN_DENSITY_WORDS;
|
|
384
|
+
const per500 = (n: number) =>
|
|
385
|
+
densityEvaluated ? Math.round((n / words) * 500 * 100) / 100 : null;
|
|
281
386
|
|
|
282
387
|
return {
|
|
283
388
|
words,
|
|
@@ -289,6 +394,7 @@ export function analyzeText(
|
|
|
289
394
|
cluster_score_per_500: per500(clusterScore),
|
|
290
395
|
dash_count: dashCount,
|
|
291
396
|
dash_density_per_500: per500(dashCount),
|
|
397
|
+
density_evaluated: densityEvaluated,
|
|
292
398
|
per_pattern: perPattern,
|
|
293
399
|
truncated,
|
|
294
400
|
hidden_unicode: hiddenUnicode,
|
|
@@ -299,9 +405,9 @@ export function exceedsThresholds(r: TellReport, t: Thresholds): string[] {
|
|
|
299
405
|
const reasons: string[] = [];
|
|
300
406
|
if (r.hard_total > t.maxHard)
|
|
301
407
|
reasons.push(`hard hits ${r.hard_total} > ${t.maxHard}`);
|
|
302
|
-
if (r.cluster_score_per_500 > t.maxScore)
|
|
408
|
+
if (r.cluster_score_per_500 !== null && r.cluster_score_per_500 > t.maxScore)
|
|
303
409
|
reasons.push(`cluster score ${r.cluster_score_per_500}/500w > ${t.maxScore}`);
|
|
304
|
-
if (r.dash_density_per_500 > t.maxDashDensity)
|
|
410
|
+
if (r.dash_density_per_500 !== null && r.dash_density_per_500 > t.maxDashDensity)
|
|
305
411
|
reasons.push(`dash density ${r.dash_density_per_500}/500w > ${t.maxDashDensity}`);
|
|
306
412
|
return reasons;
|
|
307
413
|
}
|
|
@@ -309,15 +415,31 @@ export function exceedsThresholds(r: TellReport, t: Thresholds): string[] {
|
|
|
309
415
|
function humanSummary(name: string, r: TellReport, reasons: string[]): string {
|
|
310
416
|
const lines: string[] = [];
|
|
311
417
|
const verdict = reasons.length === 0 ? "✅" : "❌";
|
|
418
|
+
const density = r.density_evaluated
|
|
419
|
+
? `cluster ${r.cluster_score_per_500}/500w · dashes ${r.dash_density_per_500}/500w`
|
|
420
|
+
: `density not evaluated (${r.words} words < ${MIN_DENSITY_WORDS}-word floor; ` +
|
|
421
|
+
`${r.cluster_score} cluster weight, ${r.dash_count} dash(es) counted)`;
|
|
312
422
|
lines.push(
|
|
313
|
-
`${verdict} ${name} — ${r.words} words (${r.language}) · hard ${r.hard_total} ·
|
|
314
|
-
`cluster ${r.cluster_score_per_500}/500w · dashes ${r.dash_density_per_500}/500w`,
|
|
423
|
+
`${verdict} ${name} — ${r.words} words (${r.language}) · hard ${r.hard_total} · ${density}`,
|
|
315
424
|
);
|
|
316
425
|
for (const h of [...r.hard_hits, ...r.cluster_hits]) {
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
426
|
+
const where = h.occurrences
|
|
427
|
+
.slice(0, 3)
|
|
428
|
+
.map((o) => `${o.line}:${o.column}`)
|
|
429
|
+
.join(", ");
|
|
430
|
+
if (h.consistency === "uniform") {
|
|
431
|
+
lines.push(
|
|
432
|
+
` ≡ ${h.id} — used consistently throughout (${h.count} occurrences, ` +
|
|
433
|
+
`${where}${h.occurrences.length > 3 ? ", …" : ""}); read as style, ` +
|
|
434
|
+
`charged once, not as ${h.count} independent tells`,
|
|
435
|
+
);
|
|
436
|
+
} else {
|
|
437
|
+
lines.push(
|
|
438
|
+
` ${h.severity === "hard" ? "‼" : "·"} ${h.id} ×${h.count}` +
|
|
439
|
+
(where ? ` @ ${where}${h.occurrences.length > 3 ? ", …" : ""}` : "") +
|
|
440
|
+
(h.samples.length ? ` (${h.samples.join(" | ")})` : ""),
|
|
441
|
+
);
|
|
442
|
+
}
|
|
321
443
|
}
|
|
322
444
|
for (const reason of reasons) lines.push(` → over threshold: ${reason}`);
|
|
323
445
|
for (const h of r.hidden_unicode) {
|
|
@@ -37,10 +37,12 @@ import { fileURLToPath } from 'node:url';
|
|
|
37
37
|
import { collectRepoAnchor, describeDrift } from './_lib/envelope_grounding.js';
|
|
38
38
|
import { readHookStdin } from './hooks/hook_stdin.js';
|
|
39
39
|
import {
|
|
40
|
-
RECYCLE_CONSUMED_REL,
|
|
41
|
-
RECYCLE_ENVELOPE_REL,
|
|
42
40
|
RECYCLE_MAX_AGE_HOURS,
|
|
41
|
+
predecessorTracePresent,
|
|
42
|
+
recycle_consumed_rel,
|
|
43
|
+
resolveContinuityRecord,
|
|
43
44
|
} from './_lib/recycle_envelope_paths.js';
|
|
45
|
+
import { env_session_id } from './sessions_cli.js';
|
|
44
46
|
import {
|
|
45
47
|
hasBoundaryMarker,
|
|
46
48
|
scanEnvelopeDirectives,
|
|
@@ -153,8 +155,33 @@ export function consume_handoff_context(root: string, now: Date = new Date()): C
|
|
|
153
155
|
* - focus (Phase 3.4): `AGENT_RESUME_FOCUS` narrows what the successor
|
|
154
156
|
* attacks first — the consumer-side mirror of `next_task`.
|
|
155
157
|
*/
|
|
156
|
-
export function consume_recycle_envelope(
|
|
157
|
-
|
|
158
|
+
export function consume_recycle_envelope(
|
|
159
|
+
root: string,
|
|
160
|
+
now: Date = new Date(),
|
|
161
|
+
session_id: string | null | undefined = undefined,
|
|
162
|
+
): ConsumeDecision {
|
|
163
|
+
// Phase 2.1: which record belongs to THIS session is a resolution, not a
|
|
164
|
+
// path constant. `resolveContinuityRecord` returns `null` with a reason
|
|
165
|
+
// when it cannot tell — the reader then starts clean and says the reason
|
|
166
|
+
// out loud rather than resuming from whichever file is newest.
|
|
167
|
+
//
|
|
168
|
+
// The id is a PARAMETER with an env fallback, not an env read. The
|
|
169
|
+
// dispatcher already carries `session_id` in the envelope, and reading the
|
|
170
|
+
// ambient variable instead means any process that inherits a host's
|
|
171
|
+
// `CLAUDE_CODE_SESSION_ID` — a test runner in an agent session, most
|
|
172
|
+
// obviously — resolves a foreign identity and reports the record absent.
|
|
173
|
+
const sessionId = session_id === undefined ? env_session_id() : session_id;
|
|
174
|
+
const override = process.env.AGENT_RECYCLE_ENVELOPE_FILE;
|
|
175
|
+
let target: string;
|
|
176
|
+
if (override !== undefined && override !== '') {
|
|
177
|
+
target = override;
|
|
178
|
+
} else {
|
|
179
|
+
const resolved = resolveContinuityRecord(root, sessionId);
|
|
180
|
+
if (resolved.file === null) {
|
|
181
|
+
return { action: 'absent', reason: resolved.reason };
|
|
182
|
+
}
|
|
183
|
+
target = resolved.file;
|
|
184
|
+
}
|
|
158
185
|
let text: string;
|
|
159
186
|
try {
|
|
160
187
|
text = fs.readFileSync(target, 'utf-8');
|
|
@@ -164,7 +191,10 @@ export function consume_recycle_envelope(root: string, now: Date = new Date()):
|
|
|
164
191
|
|
|
165
192
|
const consume = (): void => {
|
|
166
193
|
try {
|
|
167
|
-
fs.renameSync(
|
|
194
|
+
fs.renameSync(
|
|
195
|
+
target,
|
|
196
|
+
path.join(path.dirname(target), path.basename(recycle_consumed_rel(sessionId))),
|
|
197
|
+
);
|
|
168
198
|
} catch {
|
|
169
199
|
try {
|
|
170
200
|
fs.unlinkSync(target); // fallback — never let it survive
|
|
@@ -212,6 +242,19 @@ export function consume_recycle_envelope(root: string, now: Date = new Date()):
|
|
|
212
242
|
return { action: 'discard', reason: 'recycle envelope workspace does not resolve' };
|
|
213
243
|
}
|
|
214
244
|
|
|
245
|
+
// Lineage (Phase 2.3): a record that NAMES a predecessor is claiming a
|
|
246
|
+
// chain. If nothing in this workspace corroborates that session, the claim
|
|
247
|
+
// is unverifiable and the record is refused — never resolved to whatever
|
|
248
|
+
// else is lying about.
|
|
249
|
+
const predecessor = String(envelope['predecessor'] ?? '').trim();
|
|
250
|
+
if (predecessor !== '' && !predecessorTracePresent(root, predecessor)) {
|
|
251
|
+
consume();
|
|
252
|
+
return {
|
|
253
|
+
action: 'discard',
|
|
254
|
+
reason: `recycle envelope names predecessor "${predecessor}", which has no trace in this workspace`,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
215
258
|
// Drift first: a stale resume against the wrong tree is the failure the
|
|
216
259
|
// reader must see before anything else in the block.
|
|
217
260
|
const drift = describeDrift(envelope, collectRepoAnchor(root));
|
|
@@ -221,7 +264,7 @@ export function consume_recycle_envelope(root: string, now: Date = new Date()):
|
|
|
221
264
|
const focus = (process.env.AGENT_RESUME_FOCUS ?? '').trim();
|
|
222
265
|
const block = wrapAsPriorSessionData(JSON.stringify(envelope, null, 2), {
|
|
223
266
|
kind: 'recycle-envelope',
|
|
224
|
-
source:
|
|
267
|
+
source: path.relative(root, target) || path.basename(target),
|
|
225
268
|
warnings: [
|
|
226
269
|
...drift,
|
|
227
270
|
...(focus ? [`FOCUS: attack "${focus}" first — the rest of this envelope is context.`] : []),
|
|
@@ -265,7 +308,11 @@ export function main(): number {
|
|
|
265
308
|
}
|
|
266
309
|
if (event === 'session_start') {
|
|
267
310
|
const handoff = consume_handoff_context(root);
|
|
268
|
-
const recycle = consume_recycle_envelope(
|
|
311
|
+
const recycle = consume_recycle_envelope(
|
|
312
|
+
root,
|
|
313
|
+
new Date(),
|
|
314
|
+
String(envelope['session_id'] ?? '').trim() || env_session_id(),
|
|
315
|
+
);
|
|
269
316
|
const blocks: string[] = [];
|
|
270
317
|
const reasons: string[] = [];
|
|
271
318
|
// Recycle envelope first — it is the task-state restore the
|