@remnic/core 9.3.688 → 9.3.690

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.
Files changed (163) hide show
  1. package/dist/access-boundary.d.ts +5 -4
  2. package/dist/access-boundary.js +12 -11
  3. package/dist/access-cli.js +90 -26
  4. package/dist/access-cli.js.map +1 -1
  5. package/dist/access-http.d.ts +4 -3
  6. package/dist/access-http.js +15 -14
  7. package/dist/access-mcp.d.ts +11 -3
  8. package/dist/access-mcp.js +14 -13
  9. package/dist/access-operations.d.ts +11 -5
  10. package/dist/access-operations.js +15 -12
  11. package/dist/{access-service-DmCHJ4cH.d.ts → access-service-Dujr3MTm.d.ts} +62 -2
  12. package/dist/access-service.d.ts +4 -3
  13. package/dist/access-service.js +11 -10
  14. package/dist/access-surface-catalog.d.ts +4 -3
  15. package/dist/access-surface-catalog.js +2 -0
  16. package/dist/access-surface-catalog.js.map +1 -1
  17. package/dist/bootstrap.d.ts +3 -2
  18. package/dist/briefing.d.ts +1 -0
  19. package/dist/briefing.js +3 -2
  20. package/dist/buffer.d.ts +1 -0
  21. package/dist/{catalog-COqWZlZ6.d.ts → catalog-CKPtJ114.d.ts} +1 -1
  22. package/dist/causal-consolidation.js +4 -3
  23. package/dist/causal-consolidation.js.map +1 -1
  24. package/dist/{chunk-B4XVLHJA.js → chunk-2N6UNJSX.js} +2 -2
  25. package/dist/{chunk-NN7QYW5W.js → chunk-46URPRE6.js} +2 -2
  26. package/dist/chunk-46URPRE6.js.map +1 -0
  27. package/dist/{chunk-4N3TFFPH.js → chunk-54PVJDO5.js} +2 -2
  28. package/dist/{chunk-PH3HOKYW.js → chunk-67MMWC74.js} +2 -2
  29. package/dist/{chunk-STOEE37X.js → chunk-BKAMHZYR.js} +2 -2
  30. package/dist/{chunk-B43NZNMG.js → chunk-CE6CBRCV.js} +432 -2240
  31. package/dist/chunk-CE6CBRCV.js.map +1 -0
  32. package/dist/{chunk-RVYD6LR3.js → chunk-CP2NZQLT.js} +15 -4
  33. package/dist/chunk-CP2NZQLT.js.map +1 -0
  34. package/dist/{chunk-BLIWOONZ.js → chunk-CUNTLEJP.js} +4 -12
  35. package/dist/chunk-CUNTLEJP.js.map +1 -0
  36. package/dist/{chunk-2SJCWLQD.js → chunk-CWE74HRG.js} +3 -3
  37. package/dist/{chunk-VX6OBUDW.js → chunk-GR77Z2BM.js} +2 -2
  38. package/dist/{chunk-CTOQEZSN.js → chunk-LXIEXSHU.js} +2 -2
  39. package/dist/{chunk-KFBOZYME.js → chunk-NSVXK7M5.js} +33 -4
  40. package/dist/chunk-NSVXK7M5.js.map +1 -0
  41. package/dist/{chunk-473JIN2U.js → chunk-OMKUJTVJ.js} +37 -5
  42. package/dist/chunk-OMKUJTVJ.js.map +1 -0
  43. package/dist/{chunk-GS55WYRL.js → chunk-PHZKALOE.js} +2 -2
  44. package/dist/{chunk-6O6A6YUO.js → chunk-RGNEARXW.js} +3 -3
  45. package/dist/{chunk-4FE2K57M.js → chunk-RTFAZOIR.js} +2 -2
  46. package/dist/{chunk-UTYBJR7M.js → chunk-SB6CQTKP.js} +2 -2
  47. package/dist/chunk-SVOZFLIQ.js +16 -0
  48. package/dist/chunk-SVOZFLIQ.js.map +1 -0
  49. package/dist/{chunk-2KAYTPPT.js → chunk-TYF3D4MS.js} +5 -5
  50. package/dist/{chunk-WIHPNY65.js → chunk-UD5OKH4J.js} +2 -2
  51. package/dist/{chunk-QANVLERJ.js → chunk-UPIBE2DK.js} +2 -2
  52. package/dist/{chunk-CCWHPGT4.js → chunk-WROKD3XC.js} +45 -30
  53. package/dist/chunk-WROKD3XC.js.map +1 -0
  54. package/dist/{chunk-ZPTISBQU.js → chunk-X5J3JZR3.js} +7 -7
  55. package/dist/{chunk-OV4D5T7V.js → chunk-X7RLU5CR.js} +2 -2
  56. package/dist/{chunk-S2OU5DZY.js → chunk-XKUKJIOY.js} +30 -7
  57. package/dist/chunk-XKUKJIOY.js.map +1 -0
  58. package/dist/{chunk-FUCUR2OZ.js → chunk-Z7KILAOU.js} +603 -9
  59. package/dist/chunk-Z7KILAOU.js.map +1 -0
  60. package/dist/chunk-ZU7N3S6V.js +2190 -0
  61. package/dist/chunk-ZU7N3S6V.js.map +1 -0
  62. package/dist/{chunk-DCWIQFNA.js → chunk-ZUDM75KG.js} +4 -4
  63. package/dist/{cli-D8nZ2MPH.d.ts → cli-BkDp6WNi.d.ts} +2 -2
  64. package/dist/cli.d.ts +5 -4
  65. package/dist/cli.js +32 -30
  66. package/dist/compounding/engine.d.ts +1 -0
  67. package/dist/compounding/engine.js +3 -2
  68. package/dist/connectors/codex-materialize-runner.js +3 -2
  69. package/dist/connectors/index.js +3 -2
  70. package/dist/consolidation-provenance-check.d.ts +1 -0
  71. package/dist/consolidation-undo.d.ts +1 -0
  72. package/dist/contradiction/index.d.ts +1 -0
  73. package/dist/conversation-index/backend.js +2 -2
  74. package/dist/entity-retrieval.d.ts +1 -0
  75. package/dist/entity-retrieval.js +3 -2
  76. package/dist/explicit-capture.d.ts +3 -2
  77. package/dist/index.d.ts +6 -5
  78. package/dist/index.js +47 -44
  79. package/dist/index.js.map +1 -1
  80. package/dist/lcm/engine.js +3 -3
  81. package/dist/lcm/index.js +5 -5
  82. package/dist/maintenance/memory-governance.js +3 -2
  83. package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -2
  84. package/dist/maintenance/rebuild-memory-projection.js +4 -3
  85. package/dist/mcp-memory-inspector-app.d.ts +4 -3
  86. package/dist/memory-worth-outcomes.d.ts +1 -0
  87. package/dist/namespaces/migrate.d.ts +2 -1
  88. package/dist/namespaces/migrate.js +10 -9
  89. package/dist/namespaces/search.js +6 -6
  90. package/dist/namespaces/storage.d.ts +2 -1
  91. package/dist/namespaces/storage.js +3 -2
  92. package/dist/operator-toolkit.d.ts +1 -0
  93. package/dist/operator-toolkit.js +13 -11
  94. package/dist/{orchestrator-CA6ouzBn.d.ts → orchestrator-B7ixmUkP.d.ts} +145 -1
  95. package/dist/orchestrator.d.ts +3 -2
  96. package/dist/orchestrator.js +22 -20
  97. package/dist/recall-pipeline-stages.d.ts +167 -0
  98. package/dist/recall-pipeline-stages.js +61 -0
  99. package/dist/recall-pipeline-stages.js.map +1 -0
  100. package/dist/schemas.d.ts +10 -10
  101. package/dist/search/factory.js +5 -5
  102. package/dist/search/index.js +9 -9
  103. package/dist/semantic-consolidation.js +4 -3
  104. package/dist/semantic-rule-promotion.js +3 -2
  105. package/dist/semantic-rule-verifier.js +3 -2
  106. package/dist/storage.d.ts +3 -27
  107. package/dist/storage.js +5 -3
  108. package/dist/structured-attributes.d.ts +29 -0
  109. package/dist/structured-attributes.js +8 -0
  110. package/dist/structured-attributes.js.map +1 -0
  111. package/dist/temporal-supersession.d.ts +1 -0
  112. package/dist/tier-migration.d.ts +1 -0
  113. package/dist/verified-recall.js +3 -2
  114. package/package.json +2 -2
  115. package/src/access-boundary.ts +2 -1
  116. package/src/access-cli.test.ts +40 -0
  117. package/src/access-cli.ts +90 -2
  118. package/src/access-http.ts +35 -6
  119. package/src/access-mcp.ts +34 -0
  120. package/src/access-operations.ts +45 -0
  121. package/src/access-service.ts +60 -0
  122. package/src/access-surface-catalog.test.ts +1 -1
  123. package/src/access-surface-catalog.ts +2 -0
  124. package/src/cli.ts +18 -0
  125. package/src/coding/architecture-card.test.ts +544 -0
  126. package/src/coding/architecture-card.ts +687 -0
  127. package/src/coding/architecture-surfaces.test.ts +579 -0
  128. package/src/coding/architecture-surfaces.ts +457 -0
  129. package/src/maintenance/namespace-maintenance-fanout.test.ts +595 -0
  130. package/src/maintenance/namespace-maintenance-fanout.ts +318 -0
  131. package/src/maintenance/namespace-planner.ts +74 -16
  132. package/src/operator-toolkit.ts +25 -0
  133. package/src/orchestrator.ts +144 -0
  134. package/src/recall-pipeline-stages.test.ts +269 -0
  135. package/src/recall-pipeline-stages.ts +294 -0
  136. package/src/storage.ts +6 -20
  137. package/src/structured-attributes.ts +39 -0
  138. package/dist/chunk-473JIN2U.js.map +0 -1
  139. package/dist/chunk-B43NZNMG.js.map +0 -1
  140. package/dist/chunk-BLIWOONZ.js.map +0 -1
  141. package/dist/chunk-CCWHPGT4.js.map +0 -1
  142. package/dist/chunk-FUCUR2OZ.js.map +0 -1
  143. package/dist/chunk-KFBOZYME.js.map +0 -1
  144. package/dist/chunk-NN7QYW5W.js.map +0 -1
  145. package/dist/chunk-RVYD6LR3.js.map +0 -1
  146. package/dist/chunk-S2OU5DZY.js.map +0 -1
  147. /package/dist/{chunk-B4XVLHJA.js.map → chunk-2N6UNJSX.js.map} +0 -0
  148. /package/dist/{chunk-4N3TFFPH.js.map → chunk-54PVJDO5.js.map} +0 -0
  149. /package/dist/{chunk-PH3HOKYW.js.map → chunk-67MMWC74.js.map} +0 -0
  150. /package/dist/{chunk-STOEE37X.js.map → chunk-BKAMHZYR.js.map} +0 -0
  151. /package/dist/{chunk-2SJCWLQD.js.map → chunk-CWE74HRG.js.map} +0 -0
  152. /package/dist/{chunk-VX6OBUDW.js.map → chunk-GR77Z2BM.js.map} +0 -0
  153. /package/dist/{chunk-CTOQEZSN.js.map → chunk-LXIEXSHU.js.map} +0 -0
  154. /package/dist/{chunk-GS55WYRL.js.map → chunk-PHZKALOE.js.map} +0 -0
  155. /package/dist/{chunk-6O6A6YUO.js.map → chunk-RGNEARXW.js.map} +0 -0
  156. /package/dist/{chunk-4FE2K57M.js.map → chunk-RTFAZOIR.js.map} +0 -0
  157. /package/dist/{chunk-UTYBJR7M.js.map → chunk-SB6CQTKP.js.map} +0 -0
  158. /package/dist/{chunk-2KAYTPPT.js.map → chunk-TYF3D4MS.js.map} +0 -0
  159. /package/dist/{chunk-WIHPNY65.js.map → chunk-UD5OKH4J.js.map} +0 -0
  160. /package/dist/{chunk-QANVLERJ.js.map → chunk-UPIBE2DK.js.map} +0 -0
  161. /package/dist/{chunk-ZPTISBQU.js.map → chunk-X5J3JZR3.js.map} +0 -0
  162. /package/dist/{chunk-OV4D5T7V.js.map → chunk-X7RLU5CR.js.map} +0 -0
  163. /package/dist/{chunk-DCWIQFNA.js.map → chunk-ZUDM75KG.js.map} +0 -0
@@ -0,0 +1,579 @@
1
+ /**
2
+ * Surface contract tests for the architecture-card handler
3
+ * (issue #1548 Track A PR 3).
4
+ *
5
+ * Contract under test:
6
+ * - Gate predicate: enabled + architectureCard + coding context (rule 39).
7
+ * - get: returns not-found when no card exists, found+content when it does.
8
+ * - refresh: builds → persists → versions; first write vs update path.
9
+ * - Build failure → tagged failure, nothing persisted (rule 34/44).
10
+ * - Invalid subcommand → error listing valid options (rule 51).
11
+ *
12
+ * All fixtures are synthetic — no real repos (public-repo policy).
13
+ */
14
+ import assert from "node:assert/strict";
15
+ import test from "node:test";
16
+ import { type EngramAccessService } from "../access-service.js";
17
+ import { getOperation, type OperationName } from "../access-boundary.js";
18
+ import "../access-operations.js";
19
+ import { EngramMcpServer } from "../access-mcp.js";
20
+ import {
21
+ ARCHITECTURE_SUBCOMMANDS,
22
+ ARCHITECTURE_CARD_TAG,
23
+ ARCHITECTURE_CARD_KIND,
24
+ handleCodingArchitecture,
25
+ isArchitectureCardSurfaceEnabled,
26
+ isArchitectureCardSurfaceVisible,
27
+ isArchitectureSubcommand,
28
+ formatArchitectureSubcommands,
29
+ findArchitectureCardMemory,
30
+ type ArchitectureSurfaceContext,
31
+ type ArchitectureSurfaceRequest,
32
+ type ArchitectureSurfaceStorage,
33
+ } from "./architecture-surfaces.js";
34
+ import type { ArchitectureCardBuildResult } from "./architecture-card.js";
35
+ import type {
36
+ CodingKnowledgeConfig,
37
+ CodingContext,
38
+ MemoryFile,
39
+ MemoryFrontmatter,
40
+ } from "../types.js";
41
+
42
+ // ──────────────────────────────────────────────────────────────────────────
43
+ // Fixtures
44
+ // ──────────────────────────────────────────────────────────────────────────
45
+
46
+ const NOW = "2026-07-05T12:00:00.000Z";
47
+
48
+ const DEFAULT_CONFIG: CodingKnowledgeConfig = {
49
+ enabled: true,
50
+ decisionRecords: true,
51
+ architectureCard: true,
52
+ sessionDelta: true,
53
+ architectureCardLlmSummary: false,
54
+ structuralProvider: "none",
55
+ structuralProviderCommand: "",
56
+ };
57
+
58
+ const CODING_CONTEXT: CodingContext = {
59
+ projectId: "github.com/test/repo",
60
+ branch: "main",
61
+ rootPath: "/synthetic/repo",
62
+ defaultBranch: "main",
63
+ };
64
+
65
+ function makeFrontmatter(overrides: Partial<MemoryFrontmatter> = {}): MemoryFrontmatter {
66
+ return {
67
+ id: "fact-test-card",
68
+ category: "fact",
69
+ created: NOW,
70
+ updated: NOW,
71
+ source: "coding-architecture",
72
+ confidence: 1.0,
73
+ confidenceTier: "explicit",
74
+ tags: [ARCHITECTURE_CARD_TAG],
75
+ structuredAttributes: { cardKind: ARCHITECTURE_CARD_KIND },
76
+ ...overrides,
77
+ };
78
+ }
79
+
80
+ function makeCardMemory(overrides: {
81
+ content?: string;
82
+ path?: string;
83
+ frontmatter?: Partial<MemoryFrontmatter>;
84
+ } = {}): MemoryFile {
85
+ const { frontmatter: fmOverrides, content, path: memPath } = overrides;
86
+ return {
87
+ path: memPath ?? "/synthetic/facts/test-card.md",
88
+ frontmatter: makeFrontmatter(fmOverrides),
89
+ content: content ?? "# Architecture Card\n\nsynthetic",
90
+ };
91
+ }
92
+
93
+ interface StubStorage extends ArchitectureSurfaceStorage {
94
+ memories: MemoryFile[];
95
+ written: Array<{ category: string; content: string; options: Record<string, unknown> }>;
96
+ updated: Array<{ id: string; content: string }>;
97
+ }
98
+
99
+ function makeStubStorage(initialMemories: MemoryFile[] = []): StubStorage {
100
+ const allMemories: MemoryFile[] = [...initialMemories];
101
+ const written: Array<{ category: string; content: string; options: Record<string, unknown> }> = [];
102
+ const updated: Array<{ id: string; content: string }> = [];
103
+ return {
104
+ dir: "/synthetic/memory",
105
+ namespace: "default",
106
+ memories: allMemories,
107
+ written,
108
+ updated,
109
+ async readAllMemories() { return [...allMemories]; },
110
+ async writeMemory(category, content, options) {
111
+ const id = `fact-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
112
+ written.push({ category, content, options });
113
+ allMemories.push({
114
+ path: `/synthetic/facts/${id}.md`,
115
+ frontmatter: makeFrontmatter({ id, ...(options as Partial<MemoryFrontmatter>) }),
116
+ content,
117
+ });
118
+ return id;
119
+ },
120
+ async updateMemory(id, newContent) {
121
+ updated.push({ id, content: newContent });
122
+ const m = allMemories.find((mem) => mem.frontmatter.id === id);
123
+ if (m) m.content = newContent;
124
+ return !!m;
125
+ },
126
+ };
127
+ }
128
+
129
+ function makeContext(overrides: Partial<ArchitectureSurfaceContext> = {}): ArchitectureSurfaceContext {
130
+ const storage = makeStubStorage();
131
+ return {
132
+ codingKnowledge: DEFAULT_CONFIG,
133
+ getCodingContext: () => CODING_CONTEXT,
134
+ resolveStorage: async () => storage,
135
+ buildCard: async () => ({
136
+ ok: true,
137
+ card: {
138
+ content: "# Architecture Card\n\nsynthetic",
139
+ generatedAt: NOW,
140
+ byteSize: 40,
141
+ truncated: false,
142
+ },
143
+ }) satisfies ArchitectureCardBuildResult,
144
+ versioning: { snapshotIfExists: async () => {} },
145
+ throwInputError: (msg) => { throw new Error(msg); },
146
+ ...overrides,
147
+ };
148
+ }
149
+
150
+ // ──────────────────────────────────────────────────────────────────────────
151
+ // Subcommand validation
152
+ // ──────────────────────────────────────────────────────────────────────────
153
+
154
+ test("subcommands: isArchitectureSubcommand narrows valid values", () => {
155
+ assert.equal(isArchitectureSubcommand("get"), true);
156
+ assert.equal(isArchitectureSubcommand("refresh"), true);
157
+ assert.equal(isArchitectureSubcommand("delete"), false);
158
+ assert.equal(isArchitectureSubcommand(undefined), false);
159
+ assert.equal(isArchitectureSubcommand(42), false);
160
+ });
161
+
162
+ test("subcommands: formatArchitectureSubcommands lists valid options", () => {
163
+ const formatted = formatArchitectureSubcommands();
164
+ for (const sub of ARCHITECTURE_SUBCOMMANDS) {
165
+ assert.ok(formatted.includes(sub), `includes ${sub}`);
166
+ }
167
+ });
168
+
169
+ // ──────────────────────────────────────────────────────────────────────────
170
+ // Gate predicates
171
+ // ──────────────────────────────────────────────────────────────────────────
172
+
173
+ test("gate: surface enabled when config + architectureCard + coding context all true", () => {
174
+ assert.equal(isArchitectureCardSurfaceEnabled(DEFAULT_CONFIG, CODING_CONTEXT), true);
175
+ });
176
+
177
+ test("gate: surface disabled when master gate off", () => {
178
+ const config: CodingKnowledgeConfig = { ...DEFAULT_CONFIG, enabled: false };
179
+ assert.equal(isArchitectureCardSurfaceEnabled(config, CODING_CONTEXT), false);
180
+ });
181
+
182
+ test("gate: surface disabled when architectureCard off", () => {
183
+ const config: CodingKnowledgeConfig = { ...DEFAULT_CONFIG, architectureCard: false };
184
+ assert.equal(isArchitectureCardSurfaceEnabled(config, CODING_CONTEXT), false);
185
+ });
186
+
187
+ test("gate: surface disabled when no coding context", () => {
188
+ assert.equal(isArchitectureCardSurfaceEnabled(DEFAULT_CONFIG, null), false);
189
+ assert.equal(isArchitectureCardSurfaceEnabled(DEFAULT_CONFIG, undefined), false);
190
+ });
191
+
192
+ test("gate: visibility (tools/list) matches config-only check", () => {
193
+ assert.equal(isArchitectureCardSurfaceVisible(DEFAULT_CONFIG), true);
194
+ assert.equal(isArchitectureCardSurfaceVisible({ ...DEFAULT_CONFIG, enabled: false }), false);
195
+ assert.equal(isArchitectureCardSurfaceVisible({ ...DEFAULT_CONFIG, architectureCard: false }), false);
196
+ });
197
+
198
+ // ──────────────────────────────────────────────────────────────────────────
199
+ // Handler gate — throws when gate fails
200
+ // ──────────────────────────────────────────────────────────────────────────
201
+
202
+ test("handler: throws when gate is off (no coding context)", async () => {
203
+ const ctx = makeContext({ getCodingContext: () => null });
204
+ const request: ArchitectureSurfaceRequest = { subcommand: "get", sessionKey: "s1" };
205
+ await assert.rejects(
206
+ () => handleCodingArchitecture(request, ctx),
207
+ /coding_architecture requires/,
208
+ );
209
+ });
210
+
211
+ // ──────────────────────────────────────────────────────────────────────────
212
+ // get — not-found when no card exists
213
+ // ──────────────────────────────────────────────────────────────────────────
214
+
215
+ test("get: returns found=false when no card exists in namespace", async () => {
216
+ const ctx = makeContext();
217
+ const result = await handleCodingArchitecture({ subcommand: "get", sessionKey: "s1" }, ctx);
218
+ assert.equal(result.subcommand, "get");
219
+ if (result.subcommand !== "get") return;
220
+ assert.equal(result.found, false);
221
+ assert.equal(result.card, undefined);
222
+ });
223
+
224
+ // ──────────────────────────────────────────────────────────────────────────
225
+ // get — found when card exists
226
+ // ──────────────────────────────────────────────────────────────────────────
227
+
228
+ test("get: returns found=true with card content when card exists", async () => {
229
+ const cardContent = "# Architecture Card\n\nexisting content";
230
+ const storage = makeStubStorage([makeCardMemory({ content: cardContent })]);
231
+ const ctx = makeContext({ resolveStorage: async () => storage });
232
+ const result = await handleCodingArchitecture({ subcommand: "get", sessionKey: "s1" }, ctx);
233
+ assert.equal(result.subcommand, "get");
234
+ if (result.subcommand !== "get") return;
235
+ assert.equal(result.found, true);
236
+ assert.ok(result.card);
237
+ assert.equal(result.card!.content, cardContent);
238
+ });
239
+
240
+ test("get: strips the [Attributes:] suffix writeMemory appends (cursor review)", async () => {
241
+ // writeMemory appends `\n[Attributes: cardKind=architecture]` to the stored
242
+ // body when structuredAttributes are present. The card markdown returned to
243
+ // clients must NOT include that storage-metadata suffix, and byteSize must
244
+ // reflect the card alone.
245
+ const cardMarkdown = "# Architecture Card\n\nreal content";
246
+ const storedContent = `${cardMarkdown}\n[Attributes: cardKind=architecture]`;
247
+ const storage = makeStubStorage([makeCardMemory({ content: storedContent })]);
248
+ const ctx = makeContext({ resolveStorage: async () => storage });
249
+ const result = await handleCodingArchitecture({ subcommand: "get", sessionKey: "s1" }, ctx);
250
+ if (result.subcommand !== "get") return;
251
+ assert.equal(result.found, true);
252
+ assert.ok(result.card);
253
+ assert.equal(result.card!.content, cardMarkdown, "attributes suffix stripped from get content");
254
+ assert.equal(result.card!.byteSize, Buffer.byteLength(cardMarkdown, "utf-8"), "byteSize excludes the suffix");
255
+ });
256
+
257
+ test("get: shares storage's stripper — legitimate trailing attributes-like line is not truncated (cursor review)", async () => {
258
+ // The handler now uses the shared `stripAttributesSuffix` from
259
+ // structured-attributes.ts (dedup). That helper only strips a single-line
260
+ // enrichment whose payload has no premature `]` or embedded newline — so
261
+ // card markdown that legitimately ends in an attributes-LIKE line (e.g. one
262
+ // quoting a `[config]` token) is returned verbatim. The old local copy only
263
+ // checked the trailing `]` and would have sliced this content in half.
264
+ const cardMarkdown =
265
+ "# Architecture Card\n\nreal content\n[Attributes: see the [config] section]";
266
+ const storage = makeStubStorage([makeCardMemory({ content: cardMarkdown })]);
267
+ const ctx = makeContext({ resolveStorage: async () => storage });
268
+ const result = await handleCodingArchitecture({ subcommand: "get", sessionKey: "s1" }, ctx);
269
+ if (result.subcommand !== "get") return;
270
+ assert.equal(result.found, true);
271
+ assert.ok(result.card);
272
+ assert.equal(result.card!.content, cardMarkdown, "legitimate attributes-like line preserved, not truncated");
273
+ });
274
+
275
+ // ──────────────────────────────────────────────────────────────────────────
276
+ // refresh — first write (no existing card)
277
+ // ──────────────────────────────────────────────────────────────────────────
278
+
279
+ test("refresh: first card written via writeMemory, no version snapshot", async () => {
280
+ const storage = makeStubStorage();
281
+ const snapshots: MemoryFile[] = [];
282
+ const ctx = makeContext({
283
+ resolveStorage: async () => storage,
284
+ versioning: {
285
+ snapshotIfExists: async (m) => { snapshots.push(m); },
286
+ },
287
+ });
288
+ const result = await handleCodingArchitecture({ subcommand: "refresh", sessionKey: "s1" }, ctx);
289
+ assert.equal(result.subcommand, "refresh");
290
+ if (result.subcommand !== "refresh") return;
291
+ assert.equal(result.refreshed, true);
292
+ assert.ok(result.memoryId, "memoryId returned");
293
+ assert.equal(storage.written.length, 1, "one writeMemory call");
294
+ assert.equal(storage.updated.length, 0, "no updateMemory call");
295
+ assert.equal(snapshots.length, 0, "no version snapshot on first write (nothing to preserve)");
296
+ });
297
+
298
+ // ──────────────────────────────────────────────────────────────────────────
299
+ // refresh — update existing card (snapshot before overwrite, rule 25)
300
+ // ──────────────────────────────────────────────────────────────────────────
301
+
302
+ test("refresh: existing card snapshotted before update (rule 25)", async () => {
303
+ const oldCard = makeCardMemory({ content: "# OLD CARD" });
304
+ const storage = makeStubStorage([oldCard]);
305
+ const snapshots: MemoryFile[] = [];
306
+ const ctx = makeContext({
307
+ resolveStorage: async () => storage,
308
+ versioning: {
309
+ snapshotIfExists: async (m) => { snapshots.push(m); },
310
+ },
311
+ });
312
+ const result = await handleCodingArchitecture({ subcommand: "refresh", sessionKey: "s1" }, ctx);
313
+ assert.equal(result.subcommand, "refresh");
314
+ if (result.subcommand !== "refresh") return;
315
+ assert.equal(result.refreshed, true);
316
+ assert.equal(result.memoryId, oldCard.frontmatter.id, "same memory id (updated in place)");
317
+ assert.equal(snapshots.length, 1, "prior card snapshotted before update");
318
+ assert.equal(snapshots[0]!.frontmatter.id, oldCard.frontmatter.id, "snapshot is of the old card");
319
+ assert.equal(storage.updated.length, 1, "one updateMemory call");
320
+ assert.equal(storage.written.length, 0, "no writeMemory call (update path)");
321
+ });
322
+
323
+ // ──────────────────────────────────────────────────────────────────────────
324
+ // refresh — build failure → tagged failure, nothing persisted
325
+ // ──────────────────────────────────────────────────────────────────────────
326
+
327
+ test("refresh: build failure throws, nothing persisted (rule 34/44)", async () => {
328
+ const storage = makeStubStorage();
329
+ const ctx = makeContext({
330
+ resolveStorage: async () => storage,
331
+ buildCard: async () => ({
332
+ ok: false,
333
+ code: "scan_failed",
334
+ detail: "permission denied",
335
+ }) satisfies ArchitectureCardBuildResult,
336
+ });
337
+ await assert.rejects(
338
+ () => handleCodingArchitecture({ subcommand: "refresh", sessionKey: "s1" }, ctx),
339
+ (err: Error) => {
340
+ assert.match(err.message, /scan_failed/, "error carries the build code");
341
+ assert.doesNotMatch(
342
+ err.message,
343
+ /permission denied/,
344
+ "raw detail must NOT reach the client (cursor review: raw build errors)",
345
+ );
346
+ return true;
347
+ },
348
+ );
349
+ assert.equal(storage.written.length, 0, "nothing written on build failure");
350
+ assert.equal(storage.updated.length, 0, "nothing updated on build failure");
351
+ });
352
+
353
+ test("refresh: invalid_root build failure surfaces the code", async () => {
354
+ const ctx = makeContext({
355
+ buildCard: async () => ({
356
+ ok: false,
357
+ code: "invalid_root",
358
+ detail: "not a directory",
359
+ }) satisfies ArchitectureCardBuildResult,
360
+ });
361
+ await assert.rejects(
362
+ () => handleCodingArchitecture({ subcommand: "refresh", sessionKey: "s1" }, ctx),
363
+ /invalid_root/,
364
+ );
365
+ });
366
+
367
+ // ──────────────────────────────────────────────────────────────────────────
368
+ // refresh — truncated card carries the truncation tag
369
+ // ──────────────────────────────────────────────────────────────────────────
370
+
371
+ test("refresh: truncated card carries 'truncated' tag in write options", async () => {
372
+ const storage = makeStubStorage();
373
+ const ctx = makeContext({
374
+ resolveStorage: async () => storage,
375
+ buildCard: async () => ({
376
+ ok: true,
377
+ card: {
378
+ content: "# truncated card",
379
+ generatedAt: NOW,
380
+ byteSize: 4096,
381
+ truncated: true,
382
+ },
383
+ }) satisfies ArchitectureCardBuildResult,
384
+ });
385
+ const result = await handleCodingArchitecture({ subcommand: "refresh", sessionKey: "s1" }, ctx);
386
+ if (result.subcommand !== "refresh") return;
387
+ assert.equal(result.truncated, true);
388
+ // Tags are passed in the writeMemory options and the stub stores them in frontmatter.
389
+ assert.deepEqual(storage.written[0]!.options.tags, [ARCHITECTURE_CARD_TAG, "truncated"]);
390
+ const written = storage.memories[storage.memories.length - 1];
391
+ assert.ok(written?.frontmatter.tags?.includes("truncated"), "truncated tag present");
392
+ });
393
+
394
+ // ──────────────────────────────────────────────────────────────────────────
395
+ // get — truncation derived from content, not from a stale tag (cursor review)
396
+ // ──────────────────────────────────────────────────────────────────────────
397
+
398
+ test("get: truncation derived from content marker, surviving an update that left stale tags", async () => {
399
+ // Seed a card that was originally truncated (tag present) but whose
400
+ // content was later updated to a non-truncated body via updateMemory
401
+ // (which does not refresh frontmatter tags).
402
+ const storage = makeStubStorage([
403
+ makeCardMemory({
404
+ content: "# Fresh card — no truncation marker",
405
+ frontmatter: { tags: [ARCHITECTURE_CARD_TAG, "truncated"] },
406
+ }),
407
+ ]);
408
+ const ctx = makeContext({ resolveStorage: async () => storage });
409
+ const result = await handleCodingArchitecture({ subcommand: "get", sessionKey: "s1" }, ctx);
410
+ if (result.subcommand !== "get") return;
411
+ assert.equal(result.found, true);
412
+ assert.equal(result.card?.truncated, false, "content has no marker → false despite stale tag");
413
+ });
414
+
415
+ test("get: truncation true when content carries the marker", async () => {
416
+ const storage = makeStubStorage([
417
+ makeCardMemory({
418
+ content: "# Card\n\n_… card truncated to fit size cap …_",
419
+ frontmatter: { tags: [ARCHITECTURE_CARD_TAG] },
420
+ }),
421
+ ]);
422
+ const ctx = makeContext({ resolveStorage: async () => storage });
423
+ const result = await handleCodingArchitecture({ subcommand: "get", sessionKey: "s1" }, ctx);
424
+ if (result.subcommand !== "get") return;
425
+ assert.equal(result.found, true);
426
+ assert.equal(result.card?.truncated, true, "content marker present → true");
427
+ });
428
+
429
+ // ──────────────────────────────────────────────────────────────────────────
430
+ // findArchitectureCardMemory — helper
431
+ // ──────────────────────────────────────────────────────────────────────────
432
+
433
+ test("findArchitectureCardMemory: returns null when no card in namespace", async () => {
434
+ const storage = makeStubStorage([
435
+ makeCardMemory({ frontmatter: { category: "decision" } }),
436
+ ]);
437
+ const result = await findArchitectureCardMemory(storage);
438
+ assert.equal(result, null);
439
+ });
440
+
441
+ test("findArchitectureCardMemory: skips retired cards (non-active status)", async () => {
442
+ const storage = makeStubStorage([
443
+ makeCardMemory({ frontmatter: { status: "archived" } }),
444
+ ]);
445
+ const result = await findArchitectureCardMemory(storage);
446
+ assert.equal(result, null, "archived card is not found");
447
+ });
448
+
449
+ test("findArchitectureCardMemory: returns the most recently updated card", async () => {
450
+ const older = makeCardMemory({
451
+ frontmatter: { id: "old", updated: "2026-01-01T00:00:00.000Z" },
452
+ });
453
+ const newer = makeCardMemory({
454
+ frontmatter: { id: "new", updated: "2026-07-01T00:00:00.000Z" },
455
+ });
456
+ const storage = makeStubStorage([older, newer]);
457
+ const result = await findArchitectureCardMemory(storage);
458
+ assert.equal(result?.frontmatter.id, "new");
459
+ });
460
+
461
+ test("findArchitectureCardMemory: rejects tagged fact without cardKind marker (codex review)", async () => {
462
+ // A user-created fact that merely happens to be tagged "architecture-card"
463
+ // must NOT be treated as the managed card (would be overwritten on refresh).
464
+ const storage = makeStubStorage([
465
+ makeCardMemory({ frontmatter: { structuredAttributes: {} } }),
466
+ ]);
467
+ const result = await findArchitectureCardMemory(storage);
468
+ assert.equal(result, null, "tagged fact without cardKind=architecture is not the managed card");
469
+ });
470
+
471
+ test("findArchitectureCardMemory: skips cards with archivedAt set (cursor review)", async () => {
472
+ const storage = makeStubStorage([
473
+ makeCardMemory({ frontmatter: { archivedAt: "2026-01-01T00:00:00.000Z" } }),
474
+ ]);
475
+ const result = await findArchitectureCardMemory(storage);
476
+ assert.equal(result, null, "card with archivedAt is not found even without explicit status");
477
+ });
478
+
479
+ // ──────────────────────────────────────────────────────────────────────────
480
+ // Prove-fail-before: tools/list gate (rule 39)
481
+ // ──────────────────────────────────────────────────────────────────────────
482
+
483
+ /**
484
+ * Type-guarded extraction of tool names from a tools/list response.
485
+ * Narrows with `in` checks so no inline cast is needed (no-fabricated-shape
486
+ * rule). Returns the empty set on any shape the guard rejects.
487
+ */
488
+ function extractToolNames(response: unknown): Set<string> {
489
+ const names = new Set<string>();
490
+ if (typeof response !== "object" || response === null) return names;
491
+ if (!("result" in response)) return names;
492
+ const result = response.result;
493
+ if (typeof result !== "object" || result === null || Array.isArray(result)) return names;
494
+ if (!("tools" in result)) return names;
495
+ const tools = result.tools;
496
+ if (!Array.isArray(tools)) return names;
497
+ for (const tool of tools) {
498
+ if (tool !== null && typeof tool === "object" && "name" in tool && typeof tool.name === "string") {
499
+ names.add(tool.name);
500
+ }
501
+ }
502
+ return names;
503
+ }
504
+
505
+ test("tools/list: engram.coding_architecture absent when gate off (byte-identical to main)", async () => {
506
+ const stub = { briefingEnabled: true } as unknown as EngramAccessService;
507
+ const server = new EngramMcpServer(stub, { emitLegacyTools: true });
508
+ const response = await server.handleRequest({ jsonrpc: "2.0", id: 1, method: "tools/list" });
509
+ const names = extractToolNames(response);
510
+ assert.equal(names.has("engram.coding_architecture"), false, "tool must be absent when gate is off");
511
+ assert.equal(names.has("remnic.coding_architecture"), false, "alias must be absent when gate is off");
512
+ });
513
+
514
+ test("tools/list: engram.coding_architecture present when gate on", async () => {
515
+ const stub = { briefingEnabled: true } as unknown as EngramAccessService;
516
+ const server = new EngramMcpServer(stub, { emitLegacyTools: true, architectureCardVisible: true });
517
+ const response = await server.handleRequest({ jsonrpc: "2.0", id: 1, method: "tools/list" });
518
+ const names = extractToolNames(response);
519
+ assert.equal(names.has("engram.coding_architecture"), true, "tool must be advertised when gate is on");
520
+ assert.equal(names.has("remnic.coding_architecture"), true, "alias must be advertised when gate is on");
521
+ });
522
+
523
+ // ──────────────────────────────────────────────────────────────────────────
524
+ // Registry fitness: coding_architecture operation is registered
525
+ // ──────────────────────────────────────────────────────────────────────────
526
+
527
+ test("registry: coding_architecture operation is registered through the boundary", () => {
528
+ const op = getOperation("coding_architecture" as OperationName);
529
+ assert.ok(op, "coding_architecture must be registered in the operation registry");
530
+ assert.equal(op?.spec.name, "coding_architecture");
531
+ });
532
+
533
+ // ──────────────────────────────────────────────────────────────────────────
534
+ // Three surfaces → one service method (rule 22 spirit)
535
+ // ──────────────────────────────────────────────────────────────────────────
536
+
537
+ test("MCP surface: engram.coding_architecture dispatches through the boundary to service.codingArchitecture", async () => {
538
+ const calls: ArchitectureSurfaceRequest[] = [];
539
+ const service = {
540
+ codingArchitecture(req: ArchitectureSurfaceRequest): Promise<unknown> {
541
+ calls.push(req);
542
+ return Promise.resolve({ subcommand: "get", found: false });
543
+ },
544
+ } as unknown as EngramAccessService;
545
+ const server = new EngramMcpServer(service, { emitLegacyTools: true, architectureCardVisible: true });
546
+ await server.handleRequest({
547
+ jsonrpc: "2.0",
548
+ id: 1,
549
+ method: "tools/call",
550
+ params: {
551
+ name: "engram.coding_architecture",
552
+ arguments: { subcommand: "get", sessionKey: "s1" },
553
+ },
554
+ });
555
+ assert.equal(calls.length, 1, "service.codingArchitecture called exactly once");
556
+ assert.equal(calls[0]?.subcommand, "get");
557
+ });
558
+
559
+ test("MCP surface: remnic.coding_architecture alias dispatches identically", async () => {
560
+ const calls: ArchitectureSurfaceRequest[] = [];
561
+ const service = {
562
+ codingArchitecture(req: ArchitectureSurfaceRequest): Promise<unknown> {
563
+ calls.push(req);
564
+ return Promise.resolve({ subcommand: "get", found: false });
565
+ },
566
+ } as unknown as EngramAccessService;
567
+ const server = new EngramMcpServer(service, { emitLegacyTools: true, architectureCardVisible: true });
568
+ await server.handleRequest({
569
+ jsonrpc: "2.0",
570
+ id: 2,
571
+ method: "tools/call",
572
+ params: {
573
+ name: "remnic.coding_architecture",
574
+ arguments: { subcommand: "get", sessionKey: "s1" },
575
+ },
576
+ });
577
+ assert.equal(calls.length, 1, "service.codingArchitecture called exactly once via alias");
578
+ assert.equal(calls[0]?.subcommand, "get");
579
+ });