@memnexus-ai/cli 1.7.234 → 1.7.236

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.
@@ -42,6 +42,17 @@
42
42
  * stdout, and only when degraded — matching the server convention that a
43
43
  * healthy response gains no new fields (search.schema.ts).
44
44
  *
45
+ * ANCHOR-GATE SUPPRESSION (#5450) — A SECOND, DISTINCT CONDITION
46
+ *
47
+ * The fulltext-anchor gate can discard a non-empty candidate set and return `[]`
48
+ * while every branch reports healthy, so `degraded` is false. The server already
49
+ * discloses it (`meta.anchorGateSuppressed`, `meta.anchorGateDiscardedCount`);
50
+ * the CLI dropped it, and the human surfaces printed "Found 0 results". It is
51
+ * carried as `AnchorSuppressionState`, kept apart from `DegradationState`
52
+ * because a suppressed empty is not a branch failure. JSON/YAML stdout is
53
+ * UNCHANGED for this condition: the markers already ride through `meta`
54
+ * untouched, and machine callers may depend on the current shape.
55
+ *
45
56
  * This module deliberately has no imports: colouring is the caller's job, and a
46
57
  * dependency-free module keeps the type-level enforcement test cheap to run.
47
58
  */
@@ -62,6 +73,22 @@ export interface DegradationState {
62
73
  readonly degraded: boolean;
63
74
  readonly failures: readonly BranchFailure[];
64
75
  }
76
+ /**
77
+ * Whether the fulltext-anchor gate threw candidates away on this response
78
+ * (GH #5450), and how many.
79
+ *
80
+ * SEPARATE from `DegradationState` on purpose. The gate can null a candidate set
81
+ * while every retrieval branch reports ok, so `degraded` is FALSE on this path
82
+ * and the CLI's degraded warning never fires. Folding the two together would
83
+ * mislabel a healthy-but-suppressed response as a branch failure and lose the
84
+ * discarded count. `suppressed` is never optional and `discardedCount` is always
85
+ * a number, for the same reason `failures` is always an array: there is no
86
+ * `undefined` for a renderer to read as "fine".
87
+ */
88
+ export interface AnchorSuppressionState {
89
+ readonly suppressed: boolean;
90
+ readonly discardedCount: number;
91
+ }
65
92
  /** A normalised search hit: `{ memory, score, matchedVia }`. */
66
93
  export interface SearchResultItem {
67
94
  readonly memory: Record<string, any>;
@@ -72,6 +99,11 @@ export interface SearchResultItem {
72
99
  export declare const DEGRADED_MARKER = "\u26A0 Degraded retrieval";
73
100
  /** Suffix appended to a command's headline when the response is degraded. */
74
101
  export declare const DEGRADED_HEADLINE_SUFFIX = " \u2014 INCOMPLETE (degraded retrieval)";
102
+ /**
103
+ * Shared marker text for a suppressed result set. Distinct string from
104
+ * `DEGRADED_MARKER` so the two conditions stay greppable apart in agent logs.
105
+ */
106
+ export declare const SUPPRESSED_MARKER = "\u26A0 Results DISCARDED (anchor gate)";
75
107
  /**
76
108
  * The stderr warning line for a response, or null when the response is healthy.
77
109
  * Plain text — the caller applies colour.
@@ -84,6 +116,23 @@ export declare function degradationWarningLine(health: DegradationState): string
84
116
  export declare function degradationSummaryLine(health: DegradationState): string | null;
85
117
  /** Headline suffix for spinners; empty string when healthy. */
86
118
  export declare function degradationHeadlineSuffix(health: DegradationState): string;
119
+ /**
120
+ * The stderr warning line for a suppressed result set, or null when nothing was
121
+ * discarded. Plain text — the caller applies colour.
122
+ *
123
+ * The wording has one job: an agent skimming this output must not conclude that
124
+ * nothing exists. That conclusion is the actual harm in #5450 — every roleguide
125
+ * in this repo gates work on a memory search, and a cheerful "Found 0 results"
126
+ * passes that gate with a wrong answer.
127
+ */
128
+ export declare function suppressionWarningLine(suppression: AnchorSuppressionState): string | null;
129
+ /**
130
+ * A one-line marker for human stdout renderers (table footer, llm/compact
131
+ * header). Null when nothing was discarded.
132
+ */
133
+ export declare function suppressionSummaryLine(suppression: AnchorSuppressionState): string | null;
134
+ /** Headline suffix for spinners; empty string when nothing was discarded. */
135
+ export declare function suppressionHeadlineSuffix(suppression: AnchorSuppressionState): string;
87
136
  /**
88
137
  * A search response that has been through the health mapping.
89
138
  *
@@ -101,6 +150,11 @@ export declare class RenderableSearchResult {
101
150
  readonly temporalMetadata: any;
102
151
  /** Retrieval health. Always populated. */
103
152
  readonly health: DegradationState;
153
+ /**
154
+ * Anchor-gate suppression (#5450). Always populated, and deliberately NOT
155
+ * folded into `health`: a suppressed empty is not a branch failure.
156
+ */
157
+ readonly suppression: AnchorSuppressionState;
104
158
  private readonly body;
105
159
  private constructor();
106
160
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"renderable-retrieval.d.ts","sourceRoot":"","sources":["../../src/lib/renderable-retrieval.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"renderable-retrieval.d.ts","sourceRoot":"","sources":["../../src/lib/renderable-retrieval.ts"],"names":[],"mappings":"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"}
@@ -43,22 +43,45 @@
43
43
  * stdout, and only when degraded — matching the server convention that a
44
44
  * healthy response gains no new fields (search.schema.ts).
45
45
  *
46
+ * ANCHOR-GATE SUPPRESSION (#5450) — A SECOND, DISTINCT CONDITION
47
+ *
48
+ * The fulltext-anchor gate can discard a non-empty candidate set and return `[]`
49
+ * while every branch reports healthy, so `degraded` is false. The server already
50
+ * discloses it (`meta.anchorGateSuppressed`, `meta.anchorGateDiscardedCount`);
51
+ * the CLI dropped it, and the human surfaces printed "Found 0 results". It is
52
+ * carried as `AnchorSuppressionState`, kept apart from `DegradationState`
53
+ * because a suppressed empty is not a branch failure. JSON/YAML stdout is
54
+ * UNCHANGED for this condition: the markers already ride through `meta`
55
+ * untouched, and machine callers may depend on the current shape.
56
+ *
46
57
  * This module deliberately has no imports: colouring is the caller's job, and a
47
58
  * dependency-free module keeps the type-level enforcement test cheap to run.
48
59
  */
49
60
  Object.defineProperty(exports, "__esModule", { value: true });
50
- exports.RenderableContextBriefing = exports.RenderableDigestResult = exports.RenderableSearchResult = exports.DEGRADED_HEADLINE_SUFFIX = exports.DEGRADED_MARKER = void 0;
61
+ exports.RenderableContextBriefing = exports.RenderableDigestResult = exports.RenderableSearchResult = exports.SUPPRESSED_MARKER = exports.DEGRADED_HEADLINE_SUFFIX = exports.DEGRADED_MARKER = void 0;
51
62
  exports.degradationWarningLine = degradationWarningLine;
52
63
  exports.degradationSummaryLine = degradationSummaryLine;
53
64
  exports.degradationHeadlineSuffix = degradationHeadlineSuffix;
65
+ exports.suppressionWarningLine = suppressionWarningLine;
66
+ exports.suppressionSummaryLine = suppressionSummaryLine;
67
+ exports.suppressionHeadlineSuffix = suppressionHeadlineSuffix;
54
68
  /** Shared marker text. Every surface uses this string so it is greppable. */
55
69
  exports.DEGRADED_MARKER = '⚠ Degraded retrieval';
56
70
  /** Suffix appended to a command's headline when the response is degraded. */
57
71
  exports.DEGRADED_HEADLINE_SUFFIX = ' — INCOMPLETE (degraded retrieval)';
72
+ /**
73
+ * Shared marker text for a suppressed result set. Distinct string from
74
+ * `DEGRADED_MARKER` so the two conditions stay greppable apart in agent logs.
75
+ */
76
+ exports.SUPPRESSED_MARKER = '⚠ Results DISCARDED (anchor gate)';
58
77
  const HEALTHY = Object.freeze({
59
78
  degraded: false,
60
79
  failures: Object.freeze([]),
61
80
  });
81
+ const NOT_SUPPRESSED = Object.freeze({
82
+ suppressed: false,
83
+ discardedCount: 0,
84
+ });
62
85
  /**
63
86
  * Branch names are server-supplied and land on stderr, which agents read.
64
87
  * Constrain them to an identifier shape and cap the count rather than echoing
@@ -103,6 +126,29 @@ function readDegradation(container) {
103
126
  return HEALTHY;
104
127
  return Object.freeze({ degraded: true, failures: readBranchFailures(c.branchFailures) });
105
128
  }
129
+ /**
130
+ * Read the fulltext-anchor-gate disclosure from a wire `meta` container
131
+ * (`meta.anchorGateSuppressed` + `meta.anchorGateDiscardedCount`, attached by
132
+ * the SearchHealth class in core-api/src/routes/memories.routes.ts).
133
+ *
134
+ * The server was already correct: it discloses both fields. The CLI dropped them
135
+ * because `readDegradation` is keyed on `degraded`, which is false here (#5450).
136
+ * This is the single place those two field names are read.
137
+ *
138
+ * Tolerant of either field arriving alone: the count is authoritative when it is
139
+ * a positive number, and a bare `anchorGateSuppressed: true` still counts as
140
+ * suppressed (with an unknown count) rather than being silently dropped.
141
+ */
142
+ function readAnchorSuppression(container) {
143
+ const c = container;
144
+ if (!c || typeof c !== 'object')
145
+ return NOT_SUPPRESSED;
146
+ const raw = c.anchorGateDiscardedCount;
147
+ const count = typeof raw === 'number' && Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 0;
148
+ if (c.anchorGateSuppressed !== true && count === 0)
149
+ return NOT_SUPPRESSED;
150
+ return Object.freeze({ suppressed: true, discardedCount: count });
151
+ }
106
152
  /**
107
153
  * build-context reports failure per SECTION rather than per search branch
108
154
  * (`meta.timedOutSections` / `meta.failedSections`, memories.routes.ts). Map
@@ -206,6 +252,66 @@ function degradationSummaryLine(health) {
206
252
  function degradationHeadlineSuffix(health) {
207
253
  return health.degraded ? exports.DEGRADED_HEADLINE_SUFFIX : '';
208
254
  }
255
+ /**
256
+ * How the discarded count reads when the server sent the flag but no number.
257
+ *
258
+ * "CANDIDATE result", not "matching result" (#5450 review): the count is
259
+ * `anchorGateDiscardedCount`, which counts candidates the gate REJECTED, and
260
+ * rejecting weak candidates is what the gate is for — some fraction of them are
261
+ * genuinely irrelevant. Claiming N *matching* memories were withheld would send
262
+ * an agent re-querying for memories that are not there, and reporting to its own
263
+ * caller that the store holds matches it could not retrieve.
264
+ *
265
+ * The subject AND its verb are returned together ("1 candidate result was" /
266
+ * "28 candidate results were"): the verb has to agree with the singularised
267
+ * noun, so a call site that supplies its own hard-coded "were" produces
268
+ * "1 candidate result were". These lines exist to be believed by an agent
269
+ * skimming them — a visible grammar error in a warning whose whole job is to
270
+ * carry authority undermines it. Call sites must not add a verb.
271
+ */
272
+ function discardedPhrase(suppression) {
273
+ if (suppression.discardedCount === 1)
274
+ return '1 candidate result was';
275
+ return suppression.discardedCount > 0
276
+ ? `${suppression.discardedCount} candidate results were`
277
+ : 'candidate results were';
278
+ }
279
+ /**
280
+ * The stderr warning line for a suppressed result set, or null when nothing was
281
+ * discarded. Plain text — the caller applies colour.
282
+ *
283
+ * The wording has one job: an agent skimming this output must not conclude that
284
+ * nothing exists. That conclusion is the actual harm in #5450 — every roleguide
285
+ * in this repo gates work on a memory search, and a cheerful "Found 0 results"
286
+ * passes that gate with a wrong answer.
287
+ */
288
+ function suppressionWarningLine(suppression) {
289
+ if (!suppression.suppressed)
290
+ return null;
291
+ return (`${exports.SUPPRESSED_MARKER} — ${discardedPhrase(suppression)} retrieved and then DISCARDED ` +
292
+ 'by the fulltext-anchor gate before output. This result set is short or empty because ' +
293
+ 'results were THROWN AWAY, not because nothing matched. It is NOT evidence that nothing ' +
294
+ 'exists — re-run with different wording, or use --format json to read ' +
295
+ 'meta.anchorGateDiscardedCount.');
296
+ }
297
+ /**
298
+ * A one-line marker for human stdout renderers (table footer, llm/compact
299
+ * header). Null when nothing was discarded.
300
+ */
301
+ function suppressionSummaryLine(suppression) {
302
+ if (!suppression.suppressed)
303
+ return null;
304
+ return (`${exports.SUPPRESSED_MARKER} — ${discardedPhrase(suppression)} DISCARDED before output. ` +
305
+ 'An empty or short result set here is NOT evidence that nothing exists.');
306
+ }
307
+ /** Headline suffix for spinners; empty string when nothing was discarded. */
308
+ function suppressionHeadlineSuffix(suppression) {
309
+ if (!suppression.suppressed)
310
+ return '';
311
+ return suppression.discardedCount > 0
312
+ ? ` — ${suppression.discardedCount} DISCARDED, not proof of absence`
313
+ : ' — results DISCARDED, not proof of absence';
314
+ }
209
315
  function normaliseResults(raw) {
210
316
  if (!Array.isArray(raw))
211
317
  return Object.freeze([]);
@@ -223,6 +329,7 @@ class RenderableSearchResult {
223
329
  searchMethod;
224
330
  temporalMetadata;
225
331
  health;
332
+ suppression;
226
333
  body;
227
334
  constructor(
228
335
  /** Normalised `{ memory, score, matchedVia }` hits. */
@@ -234,12 +341,18 @@ class RenderableSearchResult {
234
341
  /** `meta.temporalMetadata`, when present. */
235
342
  temporalMetadata,
236
343
  /** Retrieval health. Always populated. */
237
- health, body) {
344
+ health,
345
+ /**
346
+ * Anchor-gate suppression (#5450). Always populated, and deliberately NOT
347
+ * folded into `health`: a suppressed empty is not a branch failure.
348
+ */
349
+ suppression, body) {
238
350
  this.results = results;
239
351
  this.total = total;
240
352
  this.searchMethod = searchMethod;
241
353
  this.temporalMetadata = temporalMetadata;
242
354
  this.health = health;
355
+ this.suppression = suppression;
243
356
  this.body = body;
244
357
  }
245
358
  /**
@@ -255,7 +368,7 @@ class RenderableSearchResult {
255
368
  const b = (body ?? {});
256
369
  const results = normaliseResults(b.data);
257
370
  const meta = b.meta;
258
- return new RenderableSearchResult(results, typeof meta?.total === 'number' ? meta.total : results.length, typeof meta?.searchMethod === 'string' ? meta.searchMethod : undefined, meta?.temporalMetadata, readDegradation(meta), b);
371
+ return new RenderableSearchResult(results, typeof meta?.total === 'number' ? meta.total : results.length, typeof meta?.searchMethod === 'string' ? meta.searchMethod : undefined, meta?.temporalMetadata, readDegradation(meta), readAnchorSuppression(meta), b);
259
372
  }
260
373
  /**
261
374
  * Map a `/api/memories` list body (recap's no-query path). The list endpoint
@@ -265,7 +378,7 @@ class RenderableSearchResult {
265
378
  static fromListResponse(body) {
266
379
  const b = (body ?? {});
267
380
  const results = Object.freeze((Array.isArray(b.data) ? b.data : []).map((m) => ({ memory: m })));
268
- return new RenderableSearchResult(results, results.length, undefined, undefined, HEALTHY, b);
381
+ return new RenderableSearchResult(results, results.length, undefined, undefined, HEALTHY, NOT_SUPPRESSED, b);
269
382
  }
270
383
  /**
271
384
  * The JSON/YAML payload: the response body with results normalised, plus a
@@ -1 +1 @@
1
- {"version":3,"file":"renderable-retrieval.js","sourceRoot":"","sources":["../../src/lib/renderable-retrieval.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"renderable-retrieval.js","sourceRoot":"","sources":["../../src/lib/renderable-retrieval.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDG;;;AA+NH,wDAUC;AAMD,wDAKC;AAGD,8DAEC;AAmCD,wDASC;AAMD,wDAMC;AAGD,8DAKC;AA3QD,6EAA6E;AAChE,QAAA,eAAe,GAAG,sBAAsB,CAAC;AAEtD,6EAA6E;AAChE,QAAA,wBAAwB,GAAG,oCAAoC,CAAC;AAE7E;;;GAGG;AACU,QAAA,iBAAiB,GAAG,mCAAmC,CAAC;AAErE,MAAM,OAAO,GAAqB,MAAM,CAAC,MAAM,CAAC;IAC9C,QAAQ,EAAE,KAAK;IACf,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAA6B;CACxD,CAAC,CAAC;AAEH,MAAM,cAAc,GAA2B,MAAM,CAAC,MAAM,CAAC;IAC3D,UAAU,EAAE,KAAK;IACjB,cAAc,EAAE,CAAC;CAClB,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,mBAAmB,GAAG,uBAAuB,CAAC;AACpD,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAE3B,SAAS,QAAQ,CAAC,GAAY;IAC5B,OAAO,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;AAChE,CAAC;AAED;;;;GAIG;AACH,SAAS,kBAAkB,CAAC,GAAY;IACtC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO,OAAO,CAAC,QAAQ,CAAC;IACjD,MAAM,GAAG,GAAoB,EAAE,CAAC;IAChC,KAAK,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC;QACxB,MAAM,IAAI,GACR,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,KAAa,EAAE,IAAI,IAAK,KAAa,EAAE,MAAM,CAAC,CAAC;QACvF,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAS;QAC1E,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAE,KAAa,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QAC7D,IAAI,GAAG,CAAC,MAAM,IAAI,gBAAgB;YAAE,MAAM;IAC5C,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,eAAe,CAAC,SAAkB;IACzC,MAAM,CAAC,GAAG,SAAgF,CAAC;IAC3F,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,QAAQ,KAAK,IAAI;QAAE,OAAO,OAAO,CAAC;IACvE,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,kBAAkB,CAAC,CAAC,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;AAC3F,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAS,qBAAqB,CAAC,SAAkB;IAC/C,MAAM,CAAC,GAAG,SAGG,CAAC;IACd,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,cAAc,CAAC;IACvD,MAAM,GAAG,GAAG,CAAC,CAAC,wBAAwB,CAAC;IACvC,MAAM,KAAK,GAAG,OAAO,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/F,IAAI,CAAC,CAAC,oBAAoB,KAAK,IAAI,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,cAAc,CAAC;IAC1E,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CAAC;AACpE,CAAC;AAED;;;;;GAKG;AACH,SAAS,sBAAsB,CAAC,SAAkB;IAChD,MAAM,CAAC,GAAG,SAGG,CAAC;IACd,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,QAAQ,KAAK,IAAI;QAAE,OAAO,OAAO,CAAC;IACvE,MAAM,QAAQ,GAAoB,EAAE,CAAC;IACrC,MAAM,OAAO,GAAG,CAAC,GAAY,EAAE,MAA2B,EAAE,EAAE;QAC5D,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;YAAE,OAAO;QAChC,KAAK,MAAM,IAAI,IAAI,GAAG,EAAE,CAAC;YACvB,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC;gBAAE,SAAS;YAC1E,IAAI,QAAQ,CAAC,MAAM,IAAI,gBAAgB;gBAAE,OAAO;YAChD,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QAClC,CAAC;IACH,CAAC,CAAC;IACF,OAAO,CAAC,CAAC,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAAC;IACvC,OAAO,CAAC,CAAC,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;IACnC,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AAC9E,CAAC;AAED,iFAAiF;AACjF,SAAS,aAAa,CAAC,MAAwB;IAC7C,OAAO;QACL,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;KAC3E,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,SAAS,qBAAqB,CAC5B,SAAkB,EAClB,MAAwB;IAExB,MAAM,CAAC,GAAG,SAAoC,CAAC;IAC/C,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC;IAC1C,IAAI,CAAC,CAAC,gBAAgB,IAAI,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IACvC,OAAO,EAAE,GAAG,CAAC,EAAE,cAAc,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;AAClE,CAAC;AAED;;;GAGG;AACH,SAAS,qBAAqB,CAC5B,SAAkB,EAClB,MAAwB;IAExB,MAAM,CAAC,GAAG,SAAoC,CAAC;IAC/C,IAAI,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAG,CAAC,MAA2B,EAAE,EAAE,CAC5C,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACxE,OAAO;QACL,GAAG,CAAC;QACJ,GAAG,CAAC,kBAAkB,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1E,GAAG,CAAC,gBAAgB,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACrE,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAgB,sBAAsB,CAAC,MAAwB;IAC7D,IAAI,CAAC,MAAM,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAClC,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM;QACzB,CAAC,CAAC,oBAAoB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QACxC,CAAC,CAAC,uCAAuC,CAAC;IAC5C,OAAO,CACL,GAAG,uBAAe,MAAM,MAAM,gDAAgD;QAC9E,iCAAiC,CAClC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAgB,sBAAsB,CAAC,MAAwB;IAC7D,IAAI,CAAC,MAAM,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAClC,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5D,OAAO,GAAG,uBAAe,GAAG,MAAM,kCAAkC,CAAC;AACvE,CAAC;AAED,+DAA+D;AAC/D,SAAgB,yBAAyB,CAAC,MAAwB;IAChE,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,gCAAwB,CAAC,CAAC,CAAC,EAAE,CAAC;AACzD,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,SAAS,eAAe,CAAC,WAAmC;IAC1D,IAAI,WAAW,CAAC,cAAc,KAAK,CAAC;QAAE,OAAO,wBAAwB,CAAC;IACtE,OAAO,WAAW,CAAC,cAAc,GAAG,CAAC;QACnC,CAAC,CAAC,GAAG,WAAW,CAAC,cAAc,yBAAyB;QACxD,CAAC,CAAC,wBAAwB,CAAC;AAC/B,CAAC;AAED;;;;;;;;GAQG;AACH,SAAgB,sBAAsB,CAAC,WAAmC;IACxE,IAAI,CAAC,WAAW,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IACzC,OAAO,CACL,GAAG,yBAAiB,MAAM,eAAe,CAAC,WAAW,CAAC,gCAAgC;QACtF,uFAAuF;QACvF,yFAAyF;QACzF,uEAAuE;QACvE,gCAAgC,CACjC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAgB,sBAAsB,CAAC,WAAmC;IACxE,IAAI,CAAC,WAAW,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IACzC,OAAO,CACL,GAAG,yBAAiB,MAAM,eAAe,CAAC,WAAW,CAAC,4BAA4B;QAClF,wEAAwE,CACzE,CAAC;AACJ,CAAC;AAED,6EAA6E;AAC7E,SAAgB,yBAAyB,CAAC,WAAmC;IAC3E,IAAI,CAAC,WAAW,CAAC,UAAU;QAAE,OAAO,EAAE,CAAC;IACvC,OAAO,WAAW,CAAC,cAAc,GAAG,CAAC;QACnC,CAAC,CAAC,MAAM,WAAW,CAAC,cAAc,kCAAkC;QACpE,CAAC,CAAC,4CAA4C,CAAC;AACnD,CAAC;AAED,SAAS,gBAAgB,CAAC,GAAY;IACpC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,CAAgC,CAAC;IACjF,OAAO,MAAM,CAAC,MAAM,CAClB,GAAG,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CACjB,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,EAAE,CAC9E,CACF,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAa,sBAAsB;IAGtB;IAEA;IAEA;IAEA;IAEA;IAKA;IACQ;IAhBnB;IACE,uDAAuD;IAC9C,OAAoC;IAC7C,oEAAoE;IAC3D,KAAa;IACtB,yCAAyC;IAChC,YAAgC;IACzC,6CAA6C;IACpC,gBAAqB;IAC9B,0CAA0C;IACjC,MAAwB;IACjC;;;OAGG;IACM,WAAmC,EAC3B,IAA6B;QAdrC,YAAO,GAAP,OAAO,CAA6B;QAEpC,UAAK,GAAL,KAAK,CAAQ;QAEb,iBAAY,GAAZ,YAAY,CAAoB;QAEhC,qBAAgB,GAAhB,gBAAgB,CAAK;QAErB,WAAM,GAAN,MAAM,CAAkB;QAKxB,gBAAW,GAAX,WAAW,CAAwB;QAC3B,SAAI,GAAJ,IAAI,CAAyB;IAC7C,CAAC;IAEJ;;;;;;;;OAQG;IACH,MAAM,CAAC,eAAe,CAAC,IAAa;QAClC,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAwB,CAAC;QAC9C,MAAM,OAAO,GAAG,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACzC,MAAM,IAAI,GAAG,CAAC,CAAC,IAAuC,CAAC;QACvD,OAAO,IAAI,sBAAsB,CAC/B,OAAO,EACP,OAAO,IAAI,EAAE,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAC7D,OAAO,IAAI,EAAE,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS,EACtE,IAAI,EAAE,gBAAgB,EACtB,eAAe,CAAC,IAAI,CAAC,EACrB,qBAAqB,CAAC,IAAI,CAAC,EAC3B,CAAC,CACF,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,gBAAgB,CAAC,IAAa;QACnC,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAwB,CAAC;QAC9C,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAC3B,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CACxC,CAAC;QACjC,OAAO,IAAI,sBAAsB,CAC/B,OAAO,EACP,OAAO,CAAC,MAAM,EACd,SAAS,EACT,SAAS,EACT,OAAO,EACP,cAAc,EACd,CAAC,CACF,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,cAAc;QACZ,sEAAsE;QACtE,qEAAqE;QACrE,gBAAgB;QAChB,OAAO;YACL,GAAG,IAAI,CAAC,IAAI;YACZ,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS;gBAC9B,CAAC,CAAC,EAAE,IAAI,EAAE,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE;gBAC9D,CAAC,CAAC,EAAE,CAAC;YACP,IAAI,EAAE,IAAI,CAAC,OAAO;YAClB,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxE,CAAC;IACJ,CAAC;CACF;AAlFD,wDAkFC;AAED;;;;;;GAMG;AACH,MAAa,sBAAsB;IAEtB;IAEA;IACA;IACA;IACA;IACA;IACA;IACQ;IATnB,YACW,MAAc;IACvB,2EAA2E;IAClE,QAA6B,EAC7B,OAAc,EACd,QAA2B,EAC3B,QAA2B,EAC3B,uBAA4B,EAC5B,MAAwB,EAChB,IAA6B;QARrC,WAAM,GAAN,MAAM,CAAQ;QAEd,aAAQ,GAAR,QAAQ,CAAqB;QAC7B,YAAO,GAAP,OAAO,CAAO;QACd,aAAQ,GAAR,QAAQ,CAAmB;QAC3B,aAAQ,GAAR,QAAQ,CAAmB;QAC3B,4BAAuB,GAAvB,uBAAuB,CAAK;QAC5B,WAAM,GAAN,MAAM,CAAkB;QAChB,SAAI,GAAJ,IAAI,CAAyB;IAC7C,CAAC;IAEJ,iFAAiF;IACjF,MAAM,CAAC,eAAe,CAAC,IAAa;QAClC,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAwB,CAAC;QAC9C,MAAM,QAAQ,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAwB,CAAC;QAC3D,OAAO,IAAI,sBAAsB,CAC/B,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAC5C,QAAQ,EACR,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EACzC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,EAClD,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,EAClD,CAAC,CAAC,uBAAuB,EACzB,eAAe,CAAC,QAAQ,CAAC,EACzB,CAAC,CACF,CAAC;IACJ,CAAC;IAED,cAAc;QACZ,OAAO;YACL,GAAG,IAAI,CAAC,IAAI;YACZ,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,KAAK,SAAS;gBAClC,CAAC,CAAC,EAAE,QAAQ,EAAE,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE;gBACtE,CAAC,CAAC,EAAE,CAAC;YACP,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxE,CAAC;IACJ,CAAC;CACF;AAtCD,wDAsCC;AAED;;;;;GAKG;AACH,MAAa,yBAAyB;IAGzB;IACA;IAHX;IACE,+DAA+D;IACtD,IAAyB,EACzB,MAAwB;QADxB,SAAI,GAAJ,IAAI,CAAqB;QACzB,WAAM,GAAN,MAAM,CAAkB;IAChC,CAAC;IAEJ,MAAM,CAAC,eAAe,CAAC,IAAa;QAClC,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAwB,CAAC;QAC9C,OAAO,IAAI,yBAAyB,CAAC,CAAC,EAAE,sBAAsB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAC1E,CAAC;IAED,cAAc;QACZ,OAAO;YACL,GAAG,IAAI,CAAC,IAAI;YACZ,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS;gBAC9B,CAAC,CAAC,EAAE,IAAI,EAAE,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE;gBAC9D,CAAC,CAAC,EAAE,CAAC;YACP,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxE,CAAC;IACJ,CAAC;CACF;AArBD,8DAqBC"}
@@ -201,6 +201,263 @@ $(echo "$RECENT_RESULTS" | head -c 1500)"
201
201
  # Truncate total content to ~5000 chars
202
202
  CONTENT=$(echo "$CONTENT" | head -c 5000)
203
203
 
204
+ # ── 6b. Resolve project scope (codeContext) ──────────────────────────────
205
+ # Auto-captured memories carried no scope at all, so they leaked into every
206
+ # team's scoped searches and polluted build_context (#5158, #4957). PR #3281
207
+ # fixed that for the commit path only; this is the same resolution for the
208
+ # capture hooks, using the same sources and the same precedence.
209
+ #
210
+ # Mirrors resolveCommitCodeContext() in cli/src/lib/commit-context/code-context.ts:
211
+ # 1. MX_CODE_CONTEXT env var — operator-controlled, higher trust. It wins over
212
+ # the file (CWE-345): a checked-in file from an untrusted clone must not
213
+ # override the scope the operator set explicitly.
214
+ # 2. <projectRoot>/.mx-code-context.json — repo-controlled, lower trust.
215
+ # 3. Neither present or neither usable -> NO scope. The memory is created
216
+ # unlabeled, exactly as it was before this block existed. No default is
217
+ # invented: a guessed label is invisible to the scope that should see it
218
+ # and visible to one that should not, which is worse than no label.
219
+ #
220
+ # Every command in this block must end up returning 0. The EXIT/ERR trap set in
221
+ # section 4 exits the hook, so a non-zero status here would discard the
222
+ # pre-compaction context over a missing config file — a far worse regression
223
+ # than the unlabeled memories this fixes. The absent-config path is the common
224
+ # one today.
225
+ CODE_CONTEXT_MAX_BYTES=65536
226
+
227
+ # Walk up for the git project root, mirroring findProjectRoot() in
228
+ # cli/src/lib/commit-context/local-cache.ts. `-e`, not `-d`: inside a git
229
+ # worktree `.git` is a FILE, and the worktree root — not the main repo — is the
230
+ # project root (the TS uses fs.existsSync, which makes no such distinction).
231
+ find_project_root() { # <start-dir> -> project root on stdout, or nothing
232
+ local dir
233
+ dir=$(cd "$1" 2>/dev/null && pwd -P) || return 0
234
+ while [[ -n "$dir" && "$dir" != "/" ]]; do
235
+ if [[ -e "$dir/.git" ]]; then
236
+ printf '%s' "$dir"
237
+ return 0
238
+ fi
239
+ dir=$(dirname "$dir" 2>/dev/null) || return 0
240
+ done
241
+ return 0
242
+ }
243
+
244
+ # Keep only the six known keys whose values are non-empty strings, cap each,
245
+ # and emit NOTHING when nothing usable remains — so an empty or scope-free
246
+ # object is never passed as if it were a label. Malformed JSON makes jq exit
247
+ # non-zero with no output, which lands in the same "no scope" branch.
248
+ #
249
+ # `-s` (slurp) is load-bearing, not a style choice. jq accepts a STREAM of JSON
250
+ # documents: without `-s` the filter runs once per document and `jq -c` prints
251
+ # one line each, so a config holding two concatenated or newline-separated
252
+ # objects produced a MULTI-LINE string that was passed verbatim as one
253
+ # --code-context argument. parseCodeContextFlag() (cli/src/commands/memories.ts)
254
+ # JSON.parse()s it and process.exit(1)s before any network call. In THIS hook
255
+ # that loss is unrecoverable: the transcript the capture would have summarized
256
+ # is compacted away immediately afterwards. The `!= "{}"` guard below did not
257
+ # catch it either: `{"zzz":1}{"team":"b"}` normalizes to the two lines
258
+ # `{}` + `{"team":"b"}`, and that whole string is not `{}`. Slurping into an
259
+ # array and taking the first element means exactly one line can ever be emitted.
260
+ #
261
+ # The cap is 100 CODEPOINTS, deliberately below the server's 200. The server's
262
+ # CodeContextSchema uses Zod `.max(200)`, which counts UTF-16 code units, while
263
+ # jq's `[0:N]` slices by codepoint. Every astral-plane character (emoji, CJK
264
+ # extensions, mathematical alphanumerics) is 1 codepoint but 2 UTF-16 units, so
265
+ # a `[0:200]` cap emitted values of up to 400 units — over the server maximum,
266
+ # a 400 from the API, and the same silent capture loss. Since no codepoint
267
+ # exceeds 2 UTF-16 units, 100 codepoints can never exceed 200 units. Scope
268
+ # labels are short names (product/team/service); the extra truncation is
269
+ # unreachable in practice, and truncating is recoverable where a rejected
270
+ # request is not.
271
+ normalize_code_context() { # JSON on stdin -> normalized JSON object, or nothing
272
+ local out
273
+ out=$(jq -s -c '
274
+ (.[0] // {}) as $raw
275
+ | if ($raw | type) == "object" then
276
+ reduce ("product","repo","service","component","team","role") as $k ({};
277
+ if ($raw[$k] | type) == "string" and ($raw[$k] | length) > 0
278
+ then .[$k] = ($raw[$k][0:100])
279
+ else . end)
280
+ else {} end
281
+ ' 2>/dev/null) || return 0
282
+ # The newline test is belt-and-braces: `-s` already guarantees one document,
283
+ # and jq -c escapes newlines inside string values. Nothing multi-line may ever
284
+ # reach --code-context again, whatever the filter above does.
285
+ if [[ -n "$out" && "$out" != "{}" && "$out" != "null" && "$out" != *$'\n'* ]]; then
286
+ printf '%s' "$out"
287
+ fi
288
+ return 0
289
+ }
290
+
291
+ CODE_CONTEXT_JSON=""
292
+
293
+ # 1. Env var. Tested for presence only — the value is never echoed or logged.
294
+ if [[ -n "${MX_CODE_CONTEXT:-}" ]]; then
295
+ CODE_CONTEXT_JSON=$(printf '%s' "$MX_CODE_CONTEXT" | normalize_code_context) || CODE_CONTEXT_JSON=""
296
+ fi
297
+
298
+ # 2. Repo file — consulted only when the env var yielded nothing usable.
299
+ if [[ -z "$CODE_CONTEXT_JSON" ]]; then
300
+ # ORDER IS LOAD-BEARING: harness-provided project dir, then the worktree path,
301
+ # then the cwd. Each source is TRIED IN TURN, and the transcript's directory
302
+ # is not a source at all. Both properties are corrections to earlier forms and
303
+ # both are pinned by the suite; read this before rewriting the expression.
304
+ #
305
+ # WHY EACH SOURCE IS TRIED SEPARATELY. The earlier form collapsed all three
306
+ # into ONE call: `find_project_root "${CLAUDE_PROJECT_DIR:-${CLAUDE_WORKTREE_PATH:-$(pwd)}}"`.
307
+ # `:-` substitutes only when a variable is UNSET or EMPTY — never when it is
308
+ # set to something useless. So a CLAUDE_PROJECT_DIR that is set but resolves
309
+ # to no repository (a stale path, a deleted directory, a path outside any
310
+ # checkout) consumed the single attempt and the cwd was NEVER consulted.
311
+ # Measured: CLAUDE_PROJECT_DIR=/proc and =/nonexistent/zz each produced
312
+ # {"product":"home","team":"DOTFILES-WRONG-SCOPE"} from the $HOME config.
313
+ # The loop below consults the next source instead, which `:-` cannot express.
314
+ #
315
+ # WHAT IS ACTUALLY KNOWN ABOUT CLAUDE_PROJECT_DIR IN THE CUSTOMER CONTEXT.
316
+ # It is the documented hook variable, and it is the right first source when it
317
+ # is present. This repo does NOT evidence that it is set for hooks installed
318
+ # by `mx mcp install`: every other reference to it is fleet code, and it was
319
+ # measured UNSET in a live Claude Code tool process (a tool process is not a
320
+ # hook process, so that is not disproof — but it is not confirmation either).
321
+ # The expression below is therefore designed to be CORRECT WHEN IT IS ABSENT:
322
+ # an unset or empty value is skipped by the loop and the cwd carries the case.
323
+ # Do not restructure this so that a present CLAUDE_PROJECT_DIR is required.
324
+ # WHY THE TRANSCRIPT DIRECTORY IS NOT A SOURCE. It has no legitimate resolving
325
+ # power, and the reason is DEPTH-INDEPENDENT — do not restate it as a claim
326
+ # about the transcript's nesting. An earlier version of this comment argued
327
+ # from a flat ~/.claude/projects/<slug>/<id>.jsonl layout and enumerated the
328
+ # three directories a walk could reach. That premise is false here. Measured
329
+ # on this machine: transcripts sit at BOTH depth 2 and depth 4 under
330
+ # ~/.claude/projects — 310 at depth 2, 5621 deeper — so any argument of the
331
+ # form "the walk can only ever reach these three named directories" does not
332
+ # hold. The premise that does hold, and that does not depend on depth:
333
+ #
334
+ # .git anywhere under ~/.claude, any depth: 0 (also no .hg, no .svn)
335
+ #
336
+ # An upward walk from any transcript directory traverses only directories
337
+ # inside ~/.claude, none of which carries a repository marker, so it cannot
338
+ # terminate inside that subtree at any depth. That leaves $HOME as its ONLY
339
+ # reachable non-empty outcome, which is precisely the defect this block exists
340
+ # to prevent: keeping dotfiles in git is common, so $HOME/.git exists on many
341
+ # machines, and with $HOME/.mx-code-context.json present every capture from
342
+ # every worktree on the machine was labelled from the home config. A
343
+ # confidently wrong label is worse than no label — invisible to the scope that
344
+ # should see it, visible to one that must not. Do not reintroduce it.
345
+ #
346
+ # CAVEAT, stated because this is a claim about a directory OUTSIDE this
347
+ # repository: it could stop being true, and nothing in this codebase would
348
+ # notice if a repository ever appeared under ~/.claude. Removing the leg does
349
+ # not depend on the caveat holding — the leg is simply gone — but any future
350
+ # argument to reinstate it must re-measure rather than cite this line.
351
+ #
352
+ # WHY $HOME IS REFUSED OUTRIGHT, AND WHY BOTH SIDES ARE CANONICALIZED. Belt
353
+ # and braces for the residual where the cwd itself is $HOME or a directory
354
+ # beneath it whose nearest .git is $HOME's. It also closes a consequence that
355
+ # is easy to miss: CC_ROOT is the basis of the symlink containment test below,
356
+ # so a CC_ROOT of $HOME silently widens "contained in the project" to
357
+ # "anywhere in the home directory", and $HOME/.mx-code-context.json symlinked
358
+ # to any other file under $HOME would resolve and be used. The cost is not
359
+ # supporting a user whose real project IS their home directory; that is the
360
+ # same confusion we want rejected.
361
+ #
362
+ # The comparison MUST canonicalize $HOME. CC_ROOT always arrives canonical —
363
+ # find_project_root builds it with `cd … && pwd -P` — while $HOME arrives
364
+ # verbatim from the environment, so a literal `==` against "$HOME" misses on
365
+ # every spelling that is not byte-identical. Four spellings were measured
366
+ # leaking the home label through a symlinked $HOME/.mx-code-context.json: a
367
+ # trailing slash ("/home/x/"), a "/." suffix ("/home/x/."), $HOME set to a
368
+ # SYMLINK to the real home, and a cwd reached through that same symlink.
369
+ # Symlinked home directories are routine — macOS firmlinks, NFS corporate
370
+ # homes, container images mapping /home/user onto another volume — so the
371
+ # trigger is a normal configuration and the miss is silent: the refusal does
372
+ # not fire, and the containment widening above returns with it. `cd … && pwd -P`
373
+ # is the same primitive find_project_root uses and handles all four in one step.
374
+ #
375
+ # HOME unset, or pointing at nothing, leaves _home_real empty and no refusal
376
+ # fires. That is unchanged from the previous `-n "${HOME:-}"` guard, not a
377
+ # regression. The `|| _home_real=""` is REQUIRED, not decorative: without it a
378
+ # failing `cd` leaves the assignment non-zero and last on its path, which fires
379
+ # the ERR trap and abandons the capture.
380
+ #
381
+ # ERR-TRAP SAFETY — verified, not assumed, because a regression here fires
382
+ # `trap 'exit 0' ERR` and silently abandons the capture. The loop body's only
383
+ # status-bearing statement is a `$(...)` assignment of find_project_root, which
384
+ # returns 0 on every path including the `cd` failure. The break is written as
385
+ # an `if` block for the same reason the $HOME refusal is. The earlier one-line
386
+ # form `[[ -n "$CC_ROOT" ]] && break` returns 1 whenever the loop CONTINUES —
387
+ # an earlier version of this comment claimed it returned 0, which was wrong. It
388
+ # was safe only POSITIONALLY: as a non-final command of an `&&` list it is
389
+ # exempt under the errexit rules, so nothing fired. That safety depends on
390
+ # where the statement sits and would evaporate the moment this loop became the
391
+ # last statement of a function: the function would return 1 and any `$(...)`
392
+ # call of it would fire the trap. The `if` form returns 0 and removes the
393
+ # positional dependence.
394
+ #
395
+ # The same reasoning is why the $HOME refusal is an `if` block ON PURPOSE: the
396
+ # one-line `[[ "$CC_ROOT" == "$_home_real" ]] && CC_ROOT=""` returns 1 when the
397
+ # test is false, and was measured firing the ERR trap when it landed last in an
398
+ # enclosing block. `pwd 2>/dev/null` keeps a deleted cwd from printing "error
399
+ # retrieving current directory" onto the hook's real stderr. This safety comes
400
+ # from find_project_root always returning 0, not from this hook omitting
401
+ # `set -E`, so it survives someone adding `set -E` later. Do not introduce a
402
+ # bare command on this path.
403
+ CC_ROOT=""
404
+ for _cand in "${CLAUDE_PROJECT_DIR:-}" "${CLAUDE_WORKTREE_PATH:-}" "$(pwd 2>/dev/null)"; do
405
+ [[ -n "$_cand" ]] || continue
406
+ CC_ROOT=$(find_project_root "$_cand")
407
+ if [[ -n "$CC_ROOT" ]]; then break; fi
408
+ done
409
+ _home_real=$(cd "${HOME:-/nonexistent}" 2>/dev/null && pwd -P) || _home_real=""
410
+ if [[ -n "$_home_real" && "$CC_ROOT" == "$_home_real" ]]; then
411
+ CC_ROOT=""
412
+ fi
413
+ CC_FILE="${CC_ROOT}/.mx-code-context.json"
414
+ if [[ -n "$CC_ROOT" ]]; then
415
+ # CWE-59. `-f`, `-r` and the read all follow symlinks, so a repo could check
416
+ # in `.mx-code-context.json` as a symlink to any file on the user's machine
417
+ # and the six allowlisted keys of it would be uploaded to the user's own
418
+ # MemNexus account. Resolve the path and require the result to stay inside
419
+ # the project root — the containment this same hook already applies to the
420
+ # transcript path earlier. Read through the RESOLVED path, so the link is
421
+ # not traversed a second time.
422
+ CC_REAL=$(realpath "$CC_FILE" 2>/dev/null || printf '')
423
+ if [[ -n "$CC_REAL" && "$CC_REAL" == "$CC_ROOT"/* && -f "$CC_REAL" && -r "$CC_REAL" ]]; then
424
+ # One open, bounded at the source (same 64KB bound as the TS resolver).
425
+ # The previous form size-checked with `wc -c` and then re-opened for jq:
426
+ # two opens on a path an untrusted repo controls, so a concurrent writer
427
+ # could present a small file to `wc` and a huge one to `jq` (TOCTOU) on a
428
+ # hot path. `head -c` bounds the read itself, which changes >64KB from
429
+ # "skipped" to "truncated, then rejected as invalid JSON" — the same
430
+ # observable outcome, no scope.
431
+ #
432
+ # `2>/dev/null` precedes the input redirect ON PURPOSE. Redirections apply
433
+ # left to right, so with `<"$CC_REAL" 2>/dev/null` a file that turned
434
+ # unreadable between the `-r` test and the open makes bash print the full
435
+ # path and "Permission denied" to the hook's REAL stderr, before the
436
+ # discard takes effect. Verified: reversing these two words reintroduces
437
+ # that leak. Do not reorder.
438
+ #
439
+ # The bytes are PIPED into jq, never routed through a variable. Bash
440
+ # strips NUL bytes out of command-substitution output and warns while
441
+ # doing it — from the parent shell reading the substitution, so the
442
+ # `2>/dev/null` above does not suppress that warning. Via a variable,
443
+ # `{"team":"a<NUL>b"}` printed a warning naming this script and line to
444
+ # the hook's real stderr, and then RESOLVED to team "ab": input the file
445
+ # does not contain, silently repaired before validation instead of
446
+ # rejected by it. Piping keeps the NUL inside jq, which rejects the
447
+ # document as malformed. Do not reintroduce an intermediate variable.
448
+ CODE_CONTEXT_JSON=$(head -c "$CODE_CONTEXT_MAX_BYTES" 2>/dev/null <"$CC_REAL" | normalize_code_context) || CODE_CONTEXT_JSON=""
449
+ fi
450
+ fi
451
+ fi
452
+
453
+ # Nothing resolved -> pass nothing at all, so the create command line is
454
+ # byte-identical to the pre-scope version. The `[@]+` guard keeps an empty array
455
+ # safe to expand under `set -u` on bash < 4.4.
456
+ CODE_CONTEXT_ARGS=()
457
+ if [[ -n "$CODE_CONTEXT_JSON" ]]; then
458
+ CODE_CONTEXT_ARGS=(--code-context "$CODE_CONTEXT_JSON")
459
+ fi
460
+
204
461
  # ── 7. Save memory (with timeout — never block compaction) ───────────────
205
462
  TIMEOUT_CMD=""
206
463
  if command -v timeout >/dev/null 2>&1; then
@@ -219,6 +476,7 @@ $TIMEOUT_CMD mx memories create \
219
476
  --conversation-id "NEW" \
220
477
  --content "$CONTENT" \
221
478
  --topics "auto-captured,precompact" \
479
+ "${CODE_CONTEXT_ARGS[@]+"${CODE_CONTEXT_ARGS[@]}"}" \
222
480
  >/dev/null 2>&1 || CREATE_STATUS=$?
223
481
 
224
482
  if [[ "$CREATE_STATUS" -ne 0 ]]; then