@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.
- package/dist/access-boundary.d.ts +5 -4
- package/dist/access-boundary.js +12 -11
- package/dist/access-cli.js +90 -26
- package/dist/access-cli.js.map +1 -1
- package/dist/access-http.d.ts +4 -3
- package/dist/access-http.js +15 -14
- package/dist/access-mcp.d.ts +11 -3
- package/dist/access-mcp.js +14 -13
- package/dist/access-operations.d.ts +11 -5
- package/dist/access-operations.js +15 -12
- package/dist/{access-service-DmCHJ4cH.d.ts → access-service-Dujr3MTm.d.ts} +62 -2
- package/dist/access-service.d.ts +4 -3
- package/dist/access-service.js +11 -10
- package/dist/access-surface-catalog.d.ts +4 -3
- package/dist/access-surface-catalog.js +2 -0
- package/dist/access-surface-catalog.js.map +1 -1
- package/dist/bootstrap.d.ts +3 -2
- package/dist/briefing.d.ts +1 -0
- package/dist/briefing.js +3 -2
- package/dist/buffer.d.ts +1 -0
- package/dist/{catalog-COqWZlZ6.d.ts → catalog-CKPtJ114.d.ts} +1 -1
- package/dist/causal-consolidation.js +4 -3
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/{chunk-B4XVLHJA.js → chunk-2N6UNJSX.js} +2 -2
- package/dist/{chunk-NN7QYW5W.js → chunk-46URPRE6.js} +2 -2
- package/dist/chunk-46URPRE6.js.map +1 -0
- package/dist/{chunk-4N3TFFPH.js → chunk-54PVJDO5.js} +2 -2
- package/dist/{chunk-PH3HOKYW.js → chunk-67MMWC74.js} +2 -2
- package/dist/{chunk-STOEE37X.js → chunk-BKAMHZYR.js} +2 -2
- package/dist/{chunk-B43NZNMG.js → chunk-CE6CBRCV.js} +432 -2240
- package/dist/chunk-CE6CBRCV.js.map +1 -0
- package/dist/{chunk-RVYD6LR3.js → chunk-CP2NZQLT.js} +15 -4
- package/dist/chunk-CP2NZQLT.js.map +1 -0
- package/dist/{chunk-BLIWOONZ.js → chunk-CUNTLEJP.js} +4 -12
- package/dist/chunk-CUNTLEJP.js.map +1 -0
- package/dist/{chunk-2SJCWLQD.js → chunk-CWE74HRG.js} +3 -3
- package/dist/{chunk-VX6OBUDW.js → chunk-GR77Z2BM.js} +2 -2
- package/dist/{chunk-CTOQEZSN.js → chunk-LXIEXSHU.js} +2 -2
- package/dist/{chunk-KFBOZYME.js → chunk-NSVXK7M5.js} +33 -4
- package/dist/chunk-NSVXK7M5.js.map +1 -0
- package/dist/{chunk-473JIN2U.js → chunk-OMKUJTVJ.js} +37 -5
- package/dist/chunk-OMKUJTVJ.js.map +1 -0
- package/dist/{chunk-GS55WYRL.js → chunk-PHZKALOE.js} +2 -2
- package/dist/{chunk-6O6A6YUO.js → chunk-RGNEARXW.js} +3 -3
- package/dist/{chunk-4FE2K57M.js → chunk-RTFAZOIR.js} +2 -2
- package/dist/{chunk-UTYBJR7M.js → chunk-SB6CQTKP.js} +2 -2
- package/dist/chunk-SVOZFLIQ.js +16 -0
- package/dist/chunk-SVOZFLIQ.js.map +1 -0
- package/dist/{chunk-2KAYTPPT.js → chunk-TYF3D4MS.js} +5 -5
- package/dist/{chunk-WIHPNY65.js → chunk-UD5OKH4J.js} +2 -2
- package/dist/{chunk-QANVLERJ.js → chunk-UPIBE2DK.js} +2 -2
- package/dist/{chunk-CCWHPGT4.js → chunk-WROKD3XC.js} +45 -30
- package/dist/chunk-WROKD3XC.js.map +1 -0
- package/dist/{chunk-ZPTISBQU.js → chunk-X5J3JZR3.js} +7 -7
- package/dist/{chunk-OV4D5T7V.js → chunk-X7RLU5CR.js} +2 -2
- package/dist/{chunk-S2OU5DZY.js → chunk-XKUKJIOY.js} +30 -7
- package/dist/chunk-XKUKJIOY.js.map +1 -0
- package/dist/{chunk-FUCUR2OZ.js → chunk-Z7KILAOU.js} +603 -9
- package/dist/chunk-Z7KILAOU.js.map +1 -0
- package/dist/chunk-ZU7N3S6V.js +2190 -0
- package/dist/chunk-ZU7N3S6V.js.map +1 -0
- package/dist/{chunk-DCWIQFNA.js → chunk-ZUDM75KG.js} +4 -4
- package/dist/{cli-D8nZ2MPH.d.ts → cli-BkDp6WNi.d.ts} +2 -2
- package/dist/cli.d.ts +5 -4
- package/dist/cli.js +32 -30
- package/dist/compounding/engine.d.ts +1 -0
- package/dist/compounding/engine.js +3 -2
- package/dist/connectors/codex-materialize-runner.js +3 -2
- package/dist/connectors/index.js +3 -2
- package/dist/consolidation-provenance-check.d.ts +1 -0
- package/dist/consolidation-undo.d.ts +1 -0
- package/dist/contradiction/index.d.ts +1 -0
- package/dist/conversation-index/backend.js +2 -2
- package/dist/entity-retrieval.d.ts +1 -0
- package/dist/entity-retrieval.js +3 -2
- package/dist/explicit-capture.d.ts +3 -2
- package/dist/index.d.ts +6 -5
- package/dist/index.js +47 -44
- package/dist/index.js.map +1 -1
- package/dist/lcm/engine.js +3 -3
- package/dist/lcm/index.js +5 -5
- package/dist/maintenance/memory-governance.js +3 -2
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -2
- package/dist/maintenance/rebuild-memory-projection.js +4 -3
- package/dist/mcp-memory-inspector-app.d.ts +4 -3
- package/dist/memory-worth-outcomes.d.ts +1 -0
- package/dist/namespaces/migrate.d.ts +2 -1
- package/dist/namespaces/migrate.js +10 -9
- package/dist/namespaces/search.js +6 -6
- package/dist/namespaces/storage.d.ts +2 -1
- package/dist/namespaces/storage.js +3 -2
- package/dist/operator-toolkit.d.ts +1 -0
- package/dist/operator-toolkit.js +13 -11
- package/dist/{orchestrator-CA6ouzBn.d.ts → orchestrator-B7ixmUkP.d.ts} +145 -1
- package/dist/orchestrator.d.ts +3 -2
- package/dist/orchestrator.js +22 -20
- package/dist/recall-pipeline-stages.d.ts +167 -0
- package/dist/recall-pipeline-stages.js +61 -0
- package/dist/recall-pipeline-stages.js.map +1 -0
- package/dist/schemas.d.ts +10 -10
- package/dist/search/factory.js +5 -5
- package/dist/search/index.js +9 -9
- package/dist/semantic-consolidation.js +4 -3
- package/dist/semantic-rule-promotion.js +3 -2
- package/dist/semantic-rule-verifier.js +3 -2
- package/dist/storage.d.ts +3 -27
- package/dist/storage.js +5 -3
- package/dist/structured-attributes.d.ts +29 -0
- package/dist/structured-attributes.js +8 -0
- package/dist/structured-attributes.js.map +1 -0
- package/dist/temporal-supersession.d.ts +1 -0
- package/dist/tier-migration.d.ts +1 -0
- package/dist/verified-recall.js +3 -2
- package/package.json +2 -2
- package/src/access-boundary.ts +2 -1
- package/src/access-cli.test.ts +40 -0
- package/src/access-cli.ts +90 -2
- package/src/access-http.ts +35 -6
- package/src/access-mcp.ts +34 -0
- package/src/access-operations.ts +45 -0
- package/src/access-service.ts +60 -0
- package/src/access-surface-catalog.test.ts +1 -1
- package/src/access-surface-catalog.ts +2 -0
- package/src/cli.ts +18 -0
- package/src/coding/architecture-card.test.ts +544 -0
- package/src/coding/architecture-card.ts +687 -0
- package/src/coding/architecture-surfaces.test.ts +579 -0
- package/src/coding/architecture-surfaces.ts +457 -0
- package/src/maintenance/namespace-maintenance-fanout.test.ts +595 -0
- package/src/maintenance/namespace-maintenance-fanout.ts +318 -0
- package/src/maintenance/namespace-planner.ts +74 -16
- package/src/operator-toolkit.ts +25 -0
- package/src/orchestrator.ts +144 -0
- package/src/recall-pipeline-stages.test.ts +269 -0
- package/src/recall-pipeline-stages.ts +294 -0
- package/src/storage.ts +6 -20
- package/src/structured-attributes.ts +39 -0
- package/dist/chunk-473JIN2U.js.map +0 -1
- package/dist/chunk-B43NZNMG.js.map +0 -1
- package/dist/chunk-BLIWOONZ.js.map +0 -1
- package/dist/chunk-CCWHPGT4.js.map +0 -1
- package/dist/chunk-FUCUR2OZ.js.map +0 -1
- package/dist/chunk-KFBOZYME.js.map +0 -1
- package/dist/chunk-NN7QYW5W.js.map +0 -1
- package/dist/chunk-RVYD6LR3.js.map +0 -1
- package/dist/chunk-S2OU5DZY.js.map +0 -1
- /package/dist/{chunk-B4XVLHJA.js.map → chunk-2N6UNJSX.js.map} +0 -0
- /package/dist/{chunk-4N3TFFPH.js.map → chunk-54PVJDO5.js.map} +0 -0
- /package/dist/{chunk-PH3HOKYW.js.map → chunk-67MMWC74.js.map} +0 -0
- /package/dist/{chunk-STOEE37X.js.map → chunk-BKAMHZYR.js.map} +0 -0
- /package/dist/{chunk-2SJCWLQD.js.map → chunk-CWE74HRG.js.map} +0 -0
- /package/dist/{chunk-VX6OBUDW.js.map → chunk-GR77Z2BM.js.map} +0 -0
- /package/dist/{chunk-CTOQEZSN.js.map → chunk-LXIEXSHU.js.map} +0 -0
- /package/dist/{chunk-GS55WYRL.js.map → chunk-PHZKALOE.js.map} +0 -0
- /package/dist/{chunk-6O6A6YUO.js.map → chunk-RGNEARXW.js.map} +0 -0
- /package/dist/{chunk-4FE2K57M.js.map → chunk-RTFAZOIR.js.map} +0 -0
- /package/dist/{chunk-UTYBJR7M.js.map → chunk-SB6CQTKP.js.map} +0 -0
- /package/dist/{chunk-2KAYTPPT.js.map → chunk-TYF3D4MS.js.map} +0 -0
- /package/dist/{chunk-WIHPNY65.js.map → chunk-UD5OKH4J.js.map} +0 -0
- /package/dist/{chunk-QANVLERJ.js.map → chunk-UPIBE2DK.js.map} +0 -0
- /package/dist/{chunk-ZPTISBQU.js.map → chunk-X5J3JZR3.js.map} +0 -0
- /package/dist/{chunk-OV4D5T7V.js.map → chunk-X7RLU5CR.js.map} +0 -0
- /package/dist/{chunk-DCWIQFNA.js.map → chunk-ZUDM75KG.js.map} +0 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Architecture-card surface contract + handler (issue #1548 Track A PR 3).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the decision-surfaces pattern (PR 2): one shared implementation
|
|
5
|
+
* behind the MCP, HTTP, and CLI surfaces. All three transports dispatch
|
|
6
|
+
* through the `coding_architecture` boundary operation, which calls
|
|
7
|
+
* {@link handleCodingArchitecture} via the service delegate.
|
|
8
|
+
*
|
|
9
|
+
* Gate (rule 39): `codingKnowledge.enabled + architectureCard + coding
|
|
10
|
+
* context` — one predicate, identical on every surface. The tools/list
|
|
11
|
+
* visibility gate checks conditions 1–2 only (config-level).
|
|
12
|
+
*
|
|
13
|
+
* Persistence (rule 43): the card is written through the storage
|
|
14
|
+
* manager's normal persist pipeline (writeMemory for the initial card,
|
|
15
|
+
* updateMemory for refreshes) so catalog recording + reindex fire. No
|
|
16
|
+
* direct `fs` writes of memory content. Each refresh snapshots the
|
|
17
|
+
* prior content via page-versioning before overwriting (rule 25 —
|
|
18
|
+
* preserve the old before the new is ready).
|
|
19
|
+
*
|
|
20
|
+
* Subcommands:
|
|
21
|
+
* - `get`: return the current architecture card (or not-found).
|
|
22
|
+
* - `refresh`: build a fresh card from the repo, persist it, and
|
|
23
|
+
* snapshot the prior version.
|
|
24
|
+
*/
|
|
25
|
+
import type {
|
|
26
|
+
CodingKnowledgeConfig,
|
|
27
|
+
CodingContext,
|
|
28
|
+
MemoryFile,
|
|
29
|
+
MemoryFrontmatter,
|
|
30
|
+
MemoryStatus,
|
|
31
|
+
} from "../types.js";
|
|
32
|
+
import type { VersionTrigger, VersioningConfig } from "../page-versioning.js";
|
|
33
|
+
import { stripAttributesSuffix } from "../structured-attributes.js";
|
|
34
|
+
import { ARCHITECTURE_CARD_TRUNCATION_MARKER, type ArchitectureCardBuildResult } from "./architecture-card.js";
|
|
35
|
+
import { log } from "../logger.js";
|
|
36
|
+
|
|
37
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
38
|
+
// Subcommands
|
|
39
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
export const ARCHITECTURE_SUBCOMMANDS = ["get", "refresh"] as const;
|
|
42
|
+
|
|
43
|
+
export type ArchitectureSubcommand = (typeof ARCHITECTURE_SUBCOMMANDS)[number];
|
|
44
|
+
|
|
45
|
+
const SUBCOMMAND_VALUES = ARCHITECTURE_SUBCOMMANDS as readonly string[];
|
|
46
|
+
|
|
47
|
+
/** Type guard — narrows an unknown subcommand string. */
|
|
48
|
+
export function isArchitectureSubcommand(value: unknown): value is ArchitectureSubcommand {
|
|
49
|
+
return typeof value === "string" && SUBCOMMAND_VALUES.includes(value);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Human-readable subcommand list for error messages (rule 51). */
|
|
53
|
+
export function formatArchitectureSubcommands(): string {
|
|
54
|
+
return ARCHITECTURE_SUBCOMMANDS.join(", ");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
58
|
+
// Gate predicates — rule 39: one predicate, identical on every surface
|
|
59
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The single architecture-card surface gate. Returns `true` only when:
|
|
63
|
+
* 1. `codingKnowledge.enabled` is true,
|
|
64
|
+
* 2. `codingKnowledge.architectureCard` is true,
|
|
65
|
+
* 3. a coding context is attached (so a repo root is available).
|
|
66
|
+
*/
|
|
67
|
+
export function isArchitectureCardSurfaceEnabled(
|
|
68
|
+
config: CodingKnowledgeConfig,
|
|
69
|
+
codingContext: CodingContext | null | undefined,
|
|
70
|
+
): boolean {
|
|
71
|
+
return (
|
|
72
|
+
config.enabled === true &&
|
|
73
|
+
config.architectureCard === true &&
|
|
74
|
+
codingContext !== null &&
|
|
75
|
+
codingContext !== undefined
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Config-only visibility gate — used by the MCP constructor to decide
|
|
81
|
+
* whether to advertise `engram.coding_architecture` in `tools/list`.
|
|
82
|
+
* When this returns `false` the tools array is byte-identical to
|
|
83
|
+
* pre-feature (rule 39).
|
|
84
|
+
*/
|
|
85
|
+
export function isArchitectureCardSurfaceVisible(
|
|
86
|
+
config: CodingKnowledgeConfig,
|
|
87
|
+
): boolean {
|
|
88
|
+
return config.enabled === true && config.architectureCard === true;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
92
|
+
// Surface request / response shapes
|
|
93
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
/** Canonical surface request — one shape for all three transports. */
|
|
96
|
+
export interface ArchitectureSurfaceRequest {
|
|
97
|
+
subcommand: ArchitectureSubcommand;
|
|
98
|
+
sessionKey?: string;
|
|
99
|
+
namespace?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Surface response — discriminated union on `subcommand`. */
|
|
103
|
+
export type ArchitectureSurfaceResponse =
|
|
104
|
+
| {
|
|
105
|
+
subcommand: "get";
|
|
106
|
+
found: boolean;
|
|
107
|
+
card?: {
|
|
108
|
+
content: string;
|
|
109
|
+
generatedAt: string;
|
|
110
|
+
byteSize: number;
|
|
111
|
+
truncated: boolean;
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
| {
|
|
115
|
+
subcommand: "refresh";
|
|
116
|
+
refreshed: boolean;
|
|
117
|
+
memoryId: string;
|
|
118
|
+
byteSize: number;
|
|
119
|
+
truncated: boolean;
|
|
120
|
+
buildCode?: string;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
124
|
+
// Storage contract — narrow subset of StorageManager
|
|
125
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
126
|
+
|
|
127
|
+
/** Tag identifying the architecture card memory (single card per namespace). */
|
|
128
|
+
export const ARCHITECTURE_CARD_TAG = "architecture-card";
|
|
129
|
+
|
|
130
|
+
/** Structured attribute marking a memory as the architecture card. */
|
|
131
|
+
export const ARCHITECTURE_CARD_KIND = "architecture";
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Structural subset of StorageManager the architecture handler reads or
|
|
135
|
+
* writes. Kept narrow so the module stays decoupled from storage.ts and
|
|
136
|
+
* is unit-testable with a stub.
|
|
137
|
+
*/
|
|
138
|
+
export interface ArchitectureSurfaceStorage {
|
|
139
|
+
readonly dir: string;
|
|
140
|
+
/** The resolved namespace — used for logging/observability. */
|
|
141
|
+
readonly namespace: string;
|
|
142
|
+
readAllMemories(): Promise<readonly MemoryFile[]>;
|
|
143
|
+
writeMemory(
|
|
144
|
+
category: "fact",
|
|
145
|
+
content: string,
|
|
146
|
+
options: {
|
|
147
|
+
confidence?: number;
|
|
148
|
+
tags?: string[];
|
|
149
|
+
source?: string;
|
|
150
|
+
status?: MemoryStatus;
|
|
151
|
+
structuredAttributes?: Record<string, string>;
|
|
152
|
+
},
|
|
153
|
+
): Promise<string>;
|
|
154
|
+
updateMemory(
|
|
155
|
+
id: string,
|
|
156
|
+
newContent: string,
|
|
157
|
+
options?: { supersedes?: string; lineage?: string[]; actor?: string },
|
|
158
|
+
): Promise<boolean>;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Card-builder dependency. The handler delegates repo scanning to this
|
|
163
|
+
* function so tests can inject a fixed card without touching the
|
|
164
|
+
* filesystem.
|
|
165
|
+
*/
|
|
166
|
+
export type ArchitectureCardBuilder = (
|
|
167
|
+
repoRoot: string,
|
|
168
|
+
) => Promise<ArchitectureCardBuildResult>;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Page-versioning snapshot dependency. The handler calls this BEFORE
|
|
172
|
+
* overwriting the card content so the prior version is preserved
|
|
173
|
+
* (rule 25).
|
|
174
|
+
*/
|
|
175
|
+
export interface ArchitectureVersioningHook {
|
|
176
|
+
/**
|
|
177
|
+
* Snapshot the current content of the card memory before refresh.
|
|
178
|
+
* Failures are logged but do not block the refresh (best-effort).
|
|
179
|
+
*/
|
|
180
|
+
snapshotIfExists(memory: MemoryFile): Promise<void>;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Dependencies the handler borrows from the service. The service
|
|
185
|
+
* constructs this context per call; the handler never touches the
|
|
186
|
+
* orchestrator directly.
|
|
187
|
+
*/
|
|
188
|
+
export interface ArchitectureSurfaceContext {
|
|
189
|
+
readonly codingKnowledge: CodingKnowledgeConfig;
|
|
190
|
+
getCodingContext(sessionKey: string): CodingContext | null;
|
|
191
|
+
/** Resolve storage through the SAME namespace path as memory_store. */
|
|
192
|
+
resolveStorage(request: ArchitectureSurfaceRequest): Promise<ArchitectureSurfaceStorage>;
|
|
193
|
+
/** Build the architecture card from a repo root. */
|
|
194
|
+
buildCard: ArchitectureCardBuilder;
|
|
195
|
+
/** Snapshot the prior card version before refresh (rule 25). */
|
|
196
|
+
versioning: ArchitectureVersioningHook;
|
|
197
|
+
/** Throw the surface-appropriate input-validation error. */
|
|
198
|
+
throwInputError(message: string): never;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
202
|
+
// Handler — the single shared implementation behind all three surfaces
|
|
203
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* The single shared implementation behind the MCP, HTTP, and CLI
|
|
207
|
+
* architecture-card surfaces.
|
|
208
|
+
*
|
|
209
|
+
* Gate (rule 39): `codingKnowledge.enabled + architectureCard + coding
|
|
210
|
+
* context`. Persistence (rule 43): cards are written through the storage
|
|
211
|
+
* manager's normal persist pipeline. Refresh (rule 25): the prior card
|
|
212
|
+
* is snapshotted via page-versioning BEFORE the content is overwritten.
|
|
213
|
+
*/
|
|
214
|
+
export async function handleCodingArchitecture(
|
|
215
|
+
request: ArchitectureSurfaceRequest,
|
|
216
|
+
ctx: ArchitectureSurfaceContext,
|
|
217
|
+
): Promise<ArchitectureSurfaceResponse> {
|
|
218
|
+
const codingContext = request.sessionKey
|
|
219
|
+
? ctx.getCodingContext(request.sessionKey)
|
|
220
|
+
: null;
|
|
221
|
+
// Rule 39 + type-guard narrowing: check `codingContext` inline so TS
|
|
222
|
+
// narrows it to CodingContext here, with no non-null assertion cast.
|
|
223
|
+
if (
|
|
224
|
+
codingContext === null ||
|
|
225
|
+
!isArchitectureCardSurfaceEnabled(ctx.codingKnowledge, codingContext)
|
|
226
|
+
) {
|
|
227
|
+
ctx.throwInputError(
|
|
228
|
+
"coding_architecture requires codingKnowledge.enabled, codingKnowledge.architectureCard, and an attached coding context",
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
switch (request.subcommand) {
|
|
232
|
+
case "get":
|
|
233
|
+
return architectureGet(request, ctx);
|
|
234
|
+
case "refresh":
|
|
235
|
+
return architectureRefresh(request, ctx, codingContext);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
240
|
+
// Subcommand handlers
|
|
241
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
async function architectureGet(
|
|
245
|
+
request: ArchitectureSurfaceRequest,
|
|
246
|
+
ctx: ArchitectureSurfaceContext,
|
|
247
|
+
): Promise<ArchitectureSurfaceResponse> {
|
|
248
|
+
const storage = await ctx.resolveStorage(request);
|
|
249
|
+
const existing = await findArchitectureCardMemory(storage);
|
|
250
|
+
if (!existing) {
|
|
251
|
+
return { subcommand: "get", found: false };
|
|
252
|
+
}
|
|
253
|
+
const content = stripAttributesSuffix(existing.content);
|
|
254
|
+
return {
|
|
255
|
+
subcommand: "get",
|
|
256
|
+
found: true,
|
|
257
|
+
card: {
|
|
258
|
+
content,
|
|
259
|
+
generatedAt: existing.frontmatter.updated ?? existing.frontmatter.created,
|
|
260
|
+
byteSize: Buffer.byteLength(content, "utf-8"),
|
|
261
|
+
// Derive truncation from the CONTENT marker, not a frontmatter tag —
|
|
262
|
+
// the refresh update path changes content without rewriting tags, so
|
|
263
|
+
// a tag-based check goes stale (cursor review: stale truncated tag).
|
|
264
|
+
truncated: content.includes(ARCHITECTURE_CARD_TRUNCATION_MARKER),
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function architectureRefresh(
|
|
270
|
+
request: ArchitectureSurfaceRequest,
|
|
271
|
+
ctx: ArchitectureSurfaceContext,
|
|
272
|
+
codingContext: CodingContext,
|
|
273
|
+
): Promise<ArchitectureSurfaceResponse> {
|
|
274
|
+
const repoRoot = codingContext.rootPath ?? codingContext.projectId;
|
|
275
|
+
// Authorize the write + resolve storage BEFORE scanning the repo or
|
|
276
|
+
// invoking the summariser, so an unauthorized refresh fails fast instead
|
|
277
|
+
// of doing work (and an LLM call) it cannot persist (codex review: buildCard
|
|
278
|
+
// ran before the write ACL was enforced).
|
|
279
|
+
const storage = await ctx.resolveStorage(request);
|
|
280
|
+
const buildResult = await ctx.buildCard(repoRoot);
|
|
281
|
+
if (!buildResult.ok) {
|
|
282
|
+
// A failed build is a tagged outcome, not a crash. Log the full detail
|
|
283
|
+
// for operators; it may include absolute paths / raw I/O messages that
|
|
284
|
+
// must NOT reach clients (cursor review: raw build errors reach clients).
|
|
285
|
+
log.warn(
|
|
286
|
+
`coding_architecture/refresh: card build failed (code=${buildResult.code}): ${buildResult.detail}`,
|
|
287
|
+
);
|
|
288
|
+
// Persist nothing on build failure — no stale card left behind (rule 44).
|
|
289
|
+
// Client-facing error carries only the code.
|
|
290
|
+
ctx.throwInputError(
|
|
291
|
+
`coding_architecture refresh failed: card build returned ${buildResult.code}`,
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const cardContent = buildResult.card.content;
|
|
296
|
+
const tags = [ARCHITECTURE_CARD_TAG];
|
|
297
|
+
if (buildResult.card.truncated) tags.push("truncated");
|
|
298
|
+
|
|
299
|
+
const existing = await findArchitectureCardMemory(storage);
|
|
300
|
+
if (existing) {
|
|
301
|
+
// rule 25: snapshot the prior content BEFORE overwriting.
|
|
302
|
+
await ctx.versioning.snapshotIfExists(existing);
|
|
303
|
+
const updated = await storage.updateMemory(existing.frontmatter.id, cardContent, {
|
|
304
|
+
actor: "coding-architecture-refresh",
|
|
305
|
+
});
|
|
306
|
+
if (!updated) {
|
|
307
|
+
log.warn(
|
|
308
|
+
`coding_architecture/refresh: updateMemory returned false for id=${existing.frontmatter.id} — falling back to writeMemory`,
|
|
309
|
+
);
|
|
310
|
+
const memoryId = await storage.writeMemory("fact", cardContent, {
|
|
311
|
+
confidence: 1.0,
|
|
312
|
+
tags,
|
|
313
|
+
source: "coding-architecture",
|
|
314
|
+
structuredAttributes: { cardKind: ARCHITECTURE_CARD_KIND },
|
|
315
|
+
});
|
|
316
|
+
log.info(
|
|
317
|
+
`access-write op=coding_architecture/refresh memoryId=${memoryId} (fallback write) byteSize=${buildResult.card.byteSize}`,
|
|
318
|
+
);
|
|
319
|
+
return {
|
|
320
|
+
subcommand: "refresh",
|
|
321
|
+
refreshed: true,
|
|
322
|
+
memoryId,
|
|
323
|
+
byteSize: buildResult.card.byteSize,
|
|
324
|
+
truncated: buildResult.card.truncated,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
log.info(
|
|
328
|
+
`access-write op=coding_architecture/refresh memoryId=${existing.frontmatter.id} (updated) byteSize=${buildResult.card.byteSize}`,
|
|
329
|
+
);
|
|
330
|
+
return {
|
|
331
|
+
subcommand: "refresh",
|
|
332
|
+
refreshed: true,
|
|
333
|
+
memoryId: existing.frontmatter.id,
|
|
334
|
+
byteSize: buildResult.card.byteSize,
|
|
335
|
+
truncated: buildResult.card.truncated,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// First card for this namespace — write a new memory.
|
|
340
|
+
const memoryId = await storage.writeMemory("fact", cardContent, {
|
|
341
|
+
confidence: 1.0,
|
|
342
|
+
tags,
|
|
343
|
+
source: "coding-architecture",
|
|
344
|
+
structuredAttributes: { cardKind: ARCHITECTURE_CARD_KIND },
|
|
345
|
+
});
|
|
346
|
+
log.info(
|
|
347
|
+
`access-write op=coding_architecture/refresh memoryId=${memoryId} (new) byteSize=${buildResult.card.byteSize}`,
|
|
348
|
+
);
|
|
349
|
+
return {
|
|
350
|
+
subcommand: "refresh",
|
|
351
|
+
refreshed: true,
|
|
352
|
+
memoryId,
|
|
353
|
+
byteSize: buildResult.card.byteSize,
|
|
354
|
+
truncated: buildResult.card.truncated,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
359
|
+
// Local helpers
|
|
360
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Find the existing architecture-card memory in the namespace, if any.
|
|
364
|
+
* Identified by the `architecture-card` tag + `cardKind=architecture`
|
|
365
|
+
* structured attribute. A single card per namespace is the invariant;
|
|
366
|
+
* if multiple exist (race), the most recently updated wins.
|
|
367
|
+
*/
|
|
368
|
+
export async function findArchitectureCardMemory(
|
|
369
|
+
storage: ArchitectureSurfaceStorage,
|
|
370
|
+
): Promise<MemoryFile | null> {
|
|
371
|
+
const memories = await storage.readAllMemories();
|
|
372
|
+
const cards = memories.filter((m) => {
|
|
373
|
+
if (m.frontmatter.category !== "fact") return false;
|
|
374
|
+
const tags = m.frontmatter.tags ?? [];
|
|
375
|
+
if (!tags.includes(ARCHITECTURE_CARD_TAG)) return false;
|
|
376
|
+
// Require the cardKind structured attribute so a user-created fact that
|
|
377
|
+
// merely happens to be tagged "architecture-card" is NOT mistaken for
|
|
378
|
+
// the managed card and overwritten on refresh (codex review).
|
|
379
|
+
if (m.frontmatter.structuredAttributes?.cardKind !== ARCHITECTURE_CARD_KIND) return false;
|
|
380
|
+
// Any outer status other than undefined/"active" means retired.
|
|
381
|
+
const outer = m.frontmatter.status;
|
|
382
|
+
if (outer !== undefined && outer !== "active") return false;
|
|
383
|
+
// Also skip cards archived via archivedAt without an explicit status
|
|
384
|
+
// (cursor review: archived cards can still match the tag filter).
|
|
385
|
+
if (m.frontmatter.archivedAt !== undefined && m.frontmatter.archivedAt !== null) return false;
|
|
386
|
+
return true;
|
|
387
|
+
});
|
|
388
|
+
if (cards.length === 0) return null;
|
|
389
|
+
// Most recently updated wins (deterministic tie-break by id).
|
|
390
|
+
cards.sort((a, b) => {
|
|
391
|
+
const aUpdated = a.frontmatter.updated ?? a.frontmatter.created;
|
|
392
|
+
const bUpdated = b.frontmatter.updated ?? b.frontmatter.created;
|
|
393
|
+
if (aUpdated !== bUpdated) return bUpdated.localeCompare(aUpdated);
|
|
394
|
+
return b.frontmatter.id.localeCompare(a.frontmatter.id);
|
|
395
|
+
});
|
|
396
|
+
return cards[0] ?? null;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Build a {@link MemoryFrontmatter}-shaped patch that marks a memory as
|
|
401
|
+
* the architecture card. Exported for test fixtures.
|
|
402
|
+
*/
|
|
403
|
+
export function architectureCardFrontmatterMarker(): Partial<MemoryFrontmatter> {
|
|
404
|
+
return {
|
|
405
|
+
tags: [ARCHITECTURE_CARD_TAG],
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/** Page-version snapshot writer (decoupled type — matches page-versioning.createVersion's call). */
|
|
410
|
+
type CreatePageVersion = (
|
|
411
|
+
pagePath: string,
|
|
412
|
+
content: string,
|
|
413
|
+
trigger: VersionTrigger,
|
|
414
|
+
config: VersioningConfig,
|
|
415
|
+
logger: undefined,
|
|
416
|
+
note: string,
|
|
417
|
+
memoryDir: string,
|
|
418
|
+
) => Promise<unknown>;
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Build the page-versioning snapshot hook from operator config.
|
|
422
|
+
*
|
|
423
|
+
* - Skips entirely when versioning is disabled — no sidecars written for a
|
|
424
|
+
* feature the operator turned off (codex review: hardcoded config ignored
|
|
425
|
+
* operator settings).
|
|
426
|
+
* - Reads the FULL file (frontmatter + body) from disk so a revert restores
|
|
427
|
+
* the complete memory, not just the body (codex review: snapshot was
|
|
428
|
+
* body-only).
|
|
429
|
+
*/
|
|
430
|
+
export function createArchitectureVersioningHook(
|
|
431
|
+
enabled: boolean,
|
|
432
|
+
maxVersionsPerPage: number,
|
|
433
|
+
sidecarDir: string,
|
|
434
|
+
memoryDir: string,
|
|
435
|
+
readFile: (path: string) => Promise<string>,
|
|
436
|
+
writeVersion: CreatePageVersion,
|
|
437
|
+
): ArchitectureVersioningHook {
|
|
438
|
+
return {
|
|
439
|
+
async snapshotIfExists(memory) {
|
|
440
|
+
if (!enabled) return;
|
|
441
|
+
try {
|
|
442
|
+
const fullContent = await readFile(memory.path);
|
|
443
|
+
await writeVersion(
|
|
444
|
+
memory.path,
|
|
445
|
+
fullContent,
|
|
446
|
+
"manual",
|
|
447
|
+
{ enabled: true, maxVersionsPerPage, sidecarDir },
|
|
448
|
+
undefined,
|
|
449
|
+
"architecture-card-refresh",
|
|
450
|
+
memoryDir,
|
|
451
|
+
);
|
|
452
|
+
} catch {
|
|
453
|
+
// Best-effort — a snapshot failure does not block the refresh.
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
};
|
|
457
|
+
}
|