@dsh-cc/memory 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/LICENSE +201 -0
  2. package/README.i18n.yaml +6 -0
  3. package/README.md +158 -0
  4. package/README.zh.md +125 -0
  5. package/lib/index.d.ts +75 -0
  6. package/lib/index.d.ts.map +1 -0
  7. package/lib/index.js +91 -0
  8. package/lib/index.js.map +1 -0
  9. package/lib/invariant.d.ts +16 -0
  10. package/lib/invariant.d.ts.map +1 -0
  11. package/lib/invariant.js +22 -0
  12. package/lib/invariant.js.map +1 -0
  13. package/lib/parser.d.ts +30 -0
  14. package/lib/parser.d.ts.map +1 -0
  15. package/lib/parser.js +96 -0
  16. package/lib/parser.js.map +1 -0
  17. package/lib/paths.d.ts +98 -0
  18. package/lib/paths.d.ts.map +1 -0
  19. package/lib/paths.js +236 -0
  20. package/lib/paths.js.map +1 -0
  21. package/lib/recall.d.ts +91 -0
  22. package/lib/recall.d.ts.map +1 -0
  23. package/lib/recall.js +253 -0
  24. package/lib/recall.js.map +1 -0
  25. package/lib/save.d.ts +53 -0
  26. package/lib/save.d.ts.map +1 -0
  27. package/lib/save.js +180 -0
  28. package/lib/save.js.map +1 -0
  29. package/lib/scan.d.ts +29 -0
  30. package/lib/scan.d.ts.map +1 -0
  31. package/lib/scan.js +70 -0
  32. package/lib/scan.js.map +1 -0
  33. package/lib/section.d.ts +129 -0
  34. package/lib/section.d.ts.map +1 -0
  35. package/lib/section.js +353 -0
  36. package/lib/section.js.map +1 -0
  37. package/lib/team.d.ts +90 -0
  38. package/lib/team.d.ts.map +1 -0
  39. package/lib/team.js +167 -0
  40. package/lib/team.js.map +1 -0
  41. package/lib/truncate.d.ts +35 -0
  42. package/lib/truncate.d.ts.map +1 -0
  43. package/lib/truncate.js +52 -0
  44. package/lib/truncate.js.map +1 -0
  45. package/lib/types.d.ts +34 -0
  46. package/lib/types.d.ts.map +1 -0
  47. package/lib/types.js +18 -0
  48. package/lib/types.js.map +1 -0
  49. package/lib/writeback.d.ts +85 -0
  50. package/lib/writeback.d.ts.map +1 -0
  51. package/lib/writeback.js +121 -0
  52. package/lib/writeback.js.map +1 -0
  53. package/package.json +65 -0
package/lib/index.js ADDED
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Claude Code-style file-based memory: the memdir format and parser, the
3
+ * `memory` system-prompt section, and dynamic recall by a forked side-query.
4
+ *
5
+ * All file access goes through the optional `ctx.fs` seam, so a remote or
6
+ * sandboxed backend works unchanged; a providerless host mounts memory as a
7
+ * no-op. Recall needs `ctx.subagents` and a registered one-shot provider;
8
+ * absence of either skips recall without error.
9
+ *
10
+ * @module @dsh-cc/memory
11
+ */
12
+ import z from '@deepseek-ai/schemastery';
13
+ import { toAgentOptions } from '@dsh-cc/model-aliases';
14
+ import { resolveMemoryHome } from "./paths.js";
15
+ import { MemorySection } from "./section.js";
16
+ import { MemoryRecall, SubagentMemorySelector } from "./recall.js";
17
+ import { registerMemorySaveTool } from "./save.js";
18
+ export { parseMemoryFile } from "./parser.js";
19
+ export { truncateEntrypointContent, ENTRYPOINT_NAME, MAX_ENTRYPOINT_LINES, MAX_ENTRYPOINT_BYTES } from "./truncate.js";
20
+ export { MEMORY_TYPES, parseMemoryType } from "./types.js";
21
+ export { scanMemoryDirectory } from "./scan.js";
22
+ export { resolveMemoryHome, resolveProjectMemoryRoot, resolveWorkspaceMemoryDir, canonicalMemoryRoot, projectSlug, cwdOf, gitExecSync, PROJECT_MEMORY_DIR, PROJECTS_DIR, GIT_PROBE_TIMEOUT_MS, } from "./paths.js";
23
+ export { MemorySection, renderMemorySection, renderTeamMemorySection, renderLayers, saveGuidance, MEMORY_SECTION_NAME, MEMORY_SECTION_ORDER } from "./section.js";
24
+ export { MemoryRecall, SubagentMemorySelector, extractSelectedNames, MAX_RECALL_MEMORIES } from "./recall.js";
25
+ export { TeamMemoryError, sanitizePathKey, resolveTeamMemoryRoot, validateTeamMemKey, readTeamMemFile, TEAM_MEMORY_DIR, TEAM_ENTRYPOINT_NAME } from "./team.js";
26
+ export { MEMORY_SAVE_TOOL, MEMORY_SAVE_SCOPES, MemorySaveError, pointerLine, registerMemorySaveTool, renderTopicFile, upsertPointer, } from "./save.js";
27
+ export { MEMORY_WRITES_SCHEMA, WRITEBACK_MAX_FILE_BYTES, WRITEBACK_MAX_FILES, WRITEBACK_MAX_TOTAL_BYTES, memoryWritePolicy, validateMemoryWrites, writeMemoryFiles, } from "./writeback.js";
28
+ export const name = 'memory';
29
+ /** Core services required for section registration and event listeners. */
30
+ export const inject = ['systemPrompt'];
31
+ export const Config = z.object({
32
+ memoryHome: z.string(),
33
+ sectionEnabled: z.boolean().default(true),
34
+ recallEnabled: z.boolean().default(true),
35
+ recallProviderName: z.string().default('fork'),
36
+ recallAgentOptions: z.any(),
37
+ recallUseSmallFast: z.boolean().default(false),
38
+ teamEnabled: z.boolean().default(false),
39
+ });
40
+ /**
41
+ * Resolve the recall selector's `agentOptions` stamp.
42
+ *
43
+ * Priority: explicit `recallAgentOptions` (raw overlay, NOT alias-resolved) →
44
+ * `recallUseSmallFast` + the configured `haiku` alias → nothing (inherit).
45
+ * @param c - the host context (gateway to the optional `ccModelRoutes`).
46
+ * @param config - the memory plugin config.
47
+ * @returns the `agentOptions` record, or `undefined` for inherit.
48
+ */
49
+ function resolveRecallAgentOptions(c, config) {
50
+ if (config.recallAgentOptions !== undefined)
51
+ return config.recallAgentOptions;
52
+ if (config.recallUseSmallFast !== true)
53
+ return undefined;
54
+ const routes = c.get('ccModelRoutes');
55
+ return toAgentOptions(routes?.resolve('haiku'));
56
+ }
57
+ /**
58
+ * Register the `memory` system-prompt section and recall listener.
59
+ * @param ctx - the host context carrying the system-prompt and agent seams.
60
+ * @param config - memory behavior knobs.
61
+ */
62
+ export function apply(ctx, config = {}) {
63
+ // Pass the raw configured root through: resolveMemoryHome appends `memory/`
64
+ // ONLY for undefined/empty — handing it defaultDshHome() here would resolve
65
+ // to the harness home itself and write memory files into its root.
66
+ const home = resolveMemoryHome(config.memoryHome);
67
+ if (config.sectionEnabled ?? true) {
68
+ const section = new MemorySection(ctx, home, {
69
+ teamEnabled: config.teamEnabled === true,
70
+ });
71
+ section.start();
72
+ // The save channel: memory directories sit outside every session's
73
+ // sandbox writable roots, so direct write/edit always fails; the tool
74
+ // writes host-side instead. No-op on hosts without a tools service.
75
+ registerMemorySaveTool(ctx, home, section);
76
+ // Re-scan at each turn boundary so host-side writes (memory_save,
77
+ // memory-consolidation's write-back, external edits) surface in the
78
+ // section without a restart; refresh() self-dedupes unchanged content.
79
+ ctx.on('agent/turn-stopping', ({ agent }) => {
80
+ void section.refresh(agent);
81
+ });
82
+ }
83
+ if (config.recallEnabled ?? true) {
84
+ new MemoryRecall(ctx, home, {
85
+ providerName: config.recallProviderName ?? 'fork',
86
+ enabled: true,
87
+ createSelector: (c, agent) => new SubagentMemorySelector(c, agent, config.recallProviderName ?? 'fork', resolveRecallAgentOptions(c, config)),
88
+ });
89
+ }
90
+ }
91
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Package-owned invariant companion for `@dsh-cc/memory`.
3
+ * @module @dsh-cc/memory/invariant
4
+ */
5
+ import type { Context } from '@deepseek-ai/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "memory-invariant";
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export declare const inject: string[];
10
+ /**
11
+ * Register this package's invariant companion.
12
+ * @param ctx - Cordis context carrying the invariant service.
13
+ * @returns the installed registration's disposer after setup succeeds.
14
+ */
15
+ export declare const apply: (ctx: Context) => Promise<() => void>;
16
+ //# sourceMappingURL=invariant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invariant.d.ts","sourceRoot":"","sources":["../src/invariant.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAKlD,oCAAoC;AACpC,eAAO,MAAM,IAAI,qBAAqB,CAAA;AACtC,2EAA2E;AAC3E,eAAO,MAAM,MAAM,UAAiB,CAAA;AAQpC;;;;GAIG;AACH,eAAO,MAAM,KAAK,GAAI,KAAK,OAAO,KAAG,OAAO,CAAC,MAAM,IAAI,CACU,CAAA"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Package-owned invariant companion for `@dsh-cc/memory`.
3
+ * @module @dsh-cc/memory/invariant
4
+ */
5
+ const PACKAGE_NAME = '@dsh-cc/memory';
6
+ /** Cordis companion plugin name. */
7
+ export const name = 'memory-invariant';
8
+ /** Service required before the companion can reserve package ownership. */
9
+ export const inject = ['invariants'];
10
+ /**
11
+ * No runtime invariant: recall deduplication and section freshness are
12
+ * enforced at the owning plugin seams, with no independent event/data relation.
13
+ */
14
+ const install = () => { };
15
+ /**
16
+ * Register this package's invariant companion.
17
+ * @param ctx - Cordis context carrying the invariant service.
18
+ * @returns the installed registration's disposer after setup succeeds.
19
+ */
20
+ export const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
21
+ /* jscpd:ignore-end */
22
+ //# sourceMappingURL=invariant.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invariant.js","sourceRoot":"","sources":["../src/invariant.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,YAAY,GAAG,gBAAgB,CAAA;AAErC,oCAAoC;AACpC,MAAM,CAAC,MAAM,IAAI,GAAG,kBAAkB,CAAA;AACtC,2EAA2E;AAC3E,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,YAAY,CAAC,CAAA;AAEpC;;;GAGG;AACH,MAAM,OAAO,GAAuB,GAAG,EAAE,GAAE,CAAC,CAAA;AAE5C;;;;GAIG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,GAAY,EAAuB,EAAE,CACzD,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAA;AACjE,sBAAsB"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Parsing for the memdir topic-file format: `---`-delimited YAML frontmatter
3
+ * (name/description/type) plus a Markdown body. Binary parsing and the body
4
+ * are deliberately decoupled so recall, the system-prompt section index, and
5
+ * consolidation can each consume only what they need.
6
+ * @module @dsh-cc/memory/parser
7
+ */
8
+ import { type MemoryFrontmatter } from './types.ts';
9
+ /**
10
+ * One topic file separated into its rationalized header and Markdown body.
11
+ * `undefined` marks a file that is not a valid memory topic (unreadable
12
+ * frontmatter, or missing name/description).
13
+ */
14
+ export interface ParsedMemoryFile {
15
+ /** Rationalized frontmatter header. */
16
+ frontmatter: MemoryFrontmatter;
17
+ /** The Markdown body after the closing `---`, trimmed of surrounding blanks. */
18
+ body: string;
19
+ }
20
+ /**
21
+ * Parse one topic file's raw text into its frontmatter and body.
22
+ * The header must open with a `---` line and close with a later `---` line;
23
+ * `name` and `description` must be non-empty strings; `type` is optional and
24
+ * unknown values are dropped. A malformed or incomplete file returns
25
+ * `undefined` rather than throwing.
26
+ * @param raw - the raw UTF-8 text of a memory topic file.
27
+ * @returns the parsed header/body, or `undefined` when the file is not a topic.
28
+ */
29
+ export declare function parseMemoryFile(raw: string): ParsedMemoryFile | undefined;
30
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAmB,KAAK,iBAAiB,EAAmB,MAAM,YAAY,CAAA;AAErF;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,uCAAuC;IACvC,WAAW,EAAE,iBAAiB,CAAA;IAC9B,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAWzE"}
package/lib/parser.js ADDED
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Parsing for the memdir topic-file format: `---`-delimited YAML frontmatter
3
+ * (name/description/type) plus a Markdown body. Binary parsing and the body
4
+ * are deliberately decoupled so recall, the system-prompt section index, and
5
+ * consolidation can each consume only what they need.
6
+ * @module @dsh-cc/memory/parser
7
+ */
8
+ import { parseMemoryType } from "./types.js";
9
+ /**
10
+ * Parse one topic file's raw text into its frontmatter and body.
11
+ * The header must open with a `---` line and close with a later `---` line;
12
+ * `name` and `description` must be non-empty strings; `type` is optional and
13
+ * unknown values are dropped. A malformed or incomplete file returns
14
+ * `undefined` rather than throwing.
15
+ * @param raw - the raw UTF-8 text of a memory topic file.
16
+ * @returns the parsed header/body, or `undefined` when the file is not a topic.
17
+ */
18
+ export function parseMemoryFile(raw) {
19
+ const header = parseFrontmatterHeader(raw);
20
+ if (header === undefined)
21
+ return undefined;
22
+ const fields = parseScalarFields(header.body);
23
+ const name = nonEmpty(fields.name);
24
+ const description = nonEmpty(fields.description);
25
+ if (name === undefined || description === undefined)
26
+ return undefined;
27
+ return {
28
+ frontmatter: { name, description, ...parseOptionalType(fields.type) },
29
+ body: header.after.trim(),
30
+ };
31
+ }
32
+ /**
33
+ * Split the `---`-delimited frontmatter block by scanning line-by-line.
34
+ * Returns the YAML block body and the markdown text after the closing fence.
35
+ */
36
+ function parseFrontmatterHeader(raw) {
37
+ const firstLineEnd = raw.indexOf('\n');
38
+ if (firstLineEnd < 0)
39
+ return undefined;
40
+ const firstLine = raw.slice(0, firstLineEnd).replace(/\r$/, '');
41
+ if (firstLine !== '---')
42
+ return undefined;
43
+ let lineStart = firstLineEnd + 1;
44
+ while (lineStart <= raw.length) {
45
+ const nextNewline = raw.indexOf('\n', lineStart);
46
+ const lineEnd = nextNewline < 0 ? raw.length : nextNewline;
47
+ if (raw.slice(lineStart, lineEnd).replace(/\r$/, '') === '---') {
48
+ // `lineEnd + 1` is past the closing fence and any trailing newline; when
49
+ // the fence is the last byte lineEnd === raw.length so the slice is empty.
50
+ return {
51
+ body: raw.slice(firstLineEnd + 1, lineStart),
52
+ after: raw.slice(lineEnd + 1),
53
+ };
54
+ }
55
+ if (nextNewline < 0)
56
+ return undefined;
57
+ lineStart = nextNewline + 1;
58
+ }
59
+ return undefined;
60
+ }
61
+ /** Parse the simple `key: value` scalar fields the memdir format uses. */
62
+ function parseScalarFields(yaml) {
63
+ const fields = {};
64
+ for (const rawLine of yaml.split('\n')) {
65
+ const line = rawLine.replace(/\r$/, '');
66
+ const colon = line.indexOf(':');
67
+ if (colon < 0)
68
+ continue;
69
+ const key = line.slice(0, colon).trim();
70
+ if (key !== 'name' && key !== 'description' && key !== 'type')
71
+ continue;
72
+ fields[key] = unquoteScalar(line.slice(colon + 1).trim());
73
+ }
74
+ return fields;
75
+ }
76
+ /** Strip a single surrounding pair of quotes from a scalar value. */
77
+ function unquoteScalar(value) {
78
+ if (value.length >= 2) {
79
+ const first = value[0];
80
+ const last = value[value.length - 1];
81
+ if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
82
+ return value.slice(1, -1);
83
+ }
84
+ }
85
+ return value;
86
+ }
87
+ /** Treat a missing or whitespace-only value as absent. */
88
+ function nonEmpty(value) {
89
+ const trimmed = value?.trim();
90
+ return trimmed !== undefined && trimmed.length > 0 ? trimmed : undefined;
91
+ }
92
+ function parseOptionalType(type) {
93
+ const parsed = parseMemoryType(type);
94
+ return parsed === undefined ? {} : { type: parsed };
95
+ }
96
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,eAAe,EAA2C,MAAM,YAAY,CAAA;AAcrF;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,MAAM,MAAM,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAA;IAC1C,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IAC1C,MAAM,MAAM,GAAG,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAC7C,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAClC,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;IAChD,IAAI,IAAI,KAAK,SAAS,IAAI,WAAW,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IACrE,OAAO;QACL,WAAW,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE;QACrE,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE;KAC1B,CAAA;AACH,CAAC;AAOD;;;GAGG;AACH,SAAS,sBAAsB,CAAC,GAAW;IACzC,MAAM,YAAY,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IACtC,IAAI,YAAY,GAAG,CAAC;QAAE,OAAO,SAAS,CAAA;IACtC,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;IAC/D,IAAI,SAAS,KAAK,KAAK;QAAE,OAAO,SAAS,CAAA;IACzC,IAAI,SAAS,GAAG,YAAY,GAAG,CAAC,CAAA;IAChC,OAAO,SAAS,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;QAC/B,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;QAChD,MAAM,OAAO,GAAG,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAA;QAC1D,IAAI,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC;YAC/D,yEAAyE;YACzE,2EAA2E;YAC3E,OAAO;gBACL,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,YAAY,GAAG,CAAC,EAAE,SAAS,CAAC;gBAC5C,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC;aAC9B,CAAA;QACH,CAAC;QACD,IAAI,WAAW,GAAG,CAAC;YAAE,OAAO,SAAS,CAAA;QACrC,SAAS,GAAG,WAAW,GAAG,CAAC,CAAA;IAC7B,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,0EAA0E;AAC1E,SAAS,iBAAiB,CAAC,IAAY;IACrC,MAAM,MAAM,GAAuC,EAAE,CAAA;IACrD,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;QACvC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QAC/B,IAAI,KAAK,GAAG,CAAC;YAAE,SAAQ;QACvB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,EAAE,CAAA;QACvC,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,aAAa,IAAI,GAAG,KAAK,MAAM;YAAE,SAAQ;QACvE,MAAM,CAAC,GAAG,CAAC,GAAG,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;IAC3D,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,qEAAqE;AACrE,SAAS,aAAa,CAAC,KAAa;IAClC,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QACtB,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;QACtB,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;QACpC,IAAI,CAAC,KAAK,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACvE,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;QAC3B,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED,0DAA0D;AAC1D,SAAS,QAAQ,CAAC,KAAyB;IACzC,MAAM,OAAO,GAAG,KAAK,EAAE,IAAI,EAAE,CAAA;IAC7B,OAAO,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAA;AAC1E,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAwB;IACjD,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,CAAA;IACpC,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;AACrD,CAAC"}
package/lib/paths.d.ts ADDED
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Memdir root resolution: the default harness-home memory directory plus an
3
+ * optional per-project `.claude/memory` overlay. All reads go through the
4
+ * optional `ctx.fs` seam so remote backends work unchanged.
5
+ * @module @dsh-cc/memory/paths
6
+ */
7
+ import type { Context } from '@deepseek-ai/cordis';
8
+ import type { Agent } from '@deepseek-ai/dsh-agent';
9
+ /** Directory label for the project-scoped memory overlay. */
10
+ export declare const PROJECT_MEMORY_DIR = ".claude/memory";
11
+ /** Directory label grouping the per-workspace memory directories. */
12
+ export declare const PROJECTS_DIR = "projects";
13
+ /**
14
+ * Git probe timeout for {@link canonicalMemoryRoot}. Bounded so a hung git
15
+ * (network FS, broken env) cannot stall the synchronous system-prompt
16
+ * `text` callback past the section's first-assembly budget.
17
+ */
18
+ export declare const GIT_PROBE_TIMEOUT_MS = 500;
19
+ /** A successful synchronous git invocation. */
20
+ export interface MemoryGitExecResult {
21
+ stdout: string;
22
+ }
23
+ /**
24
+ * Run one git argv in `cwd`, synchronously. Returns undefined on spawn
25
+ * failure, non-zero exit, or timeout — the caller treats that as "not a git
26
+ * repository". Injectable so tests script the git conversation.
27
+ */
28
+ export type MemoryGitExec = (argv: readonly string[], cwd: string) => MemoryGitExecResult | undefined;
29
+ /** The default exec: real `git` via spawnSync with a bounded timeout. */
30
+ export declare function gitExecSync(argv: readonly string[], cwd: string): MemoryGitExecResult | undefined;
31
+ /**
32
+ * Resolve the default (harness-home) memory root. A configured root wins;
33
+ * otherwise the shared harness home.
34
+ * @param configured - an explicit root, or `undefined` for the default.
35
+ * @returns the resolved harness-home memory directory.
36
+ */
37
+ export declare function resolveMemoryHome(configured?: string): string;
38
+ /**
39
+ * Encode a workspace path as a single filesystem-safe directory name. Ported
40
+ * from upstream `projectKey` (`session-persistence-jsonl/src/format.ts`),
41
+ * minus the `--` wrapper: separators and drive colons collapse to `-`, unsafe
42
+ * code units escape as `~XXXX`, leading dashes strip, and the result
43
+ * truncates to 251 chars (fallback `root`). The input is the canonical git
44
+ * root from {@link canonicalMemoryRoot}, so worktrees of one repo share a
45
+ * slug even though session transcripts still group by raw cwd.
46
+ * @param cwd - the workspace path to encode.
47
+ * @returns the workspace slug.
48
+ */
49
+ export declare function projectSlug(cwd: string): string;
50
+ /** Test hook: drop the module-level memo (also safe as a no-op cleanup). */
51
+ export declare function __clearMemoryRootCache(): void;
52
+ /**
53
+ * The canonical git-repo root a cwd's workspace memories belong to.
54
+ *
55
+ * Matches Claude Code auto-memory identity: derived from the git repository
56
+ * so worktrees and subdirectories share one store. A linked worktree
57
+ * collapses onto the main checkout (`dirname(git-common-dir)`); a submodule
58
+ * keeps its own toplevel. Non-git / git failure / timeout degrades to
59
+ * `resolve(cwd)` — today's pre-collapse behavior.
60
+ *
61
+ * Results are memoised per resolved `cwd` so the synchronous system-prompt
62
+ * section probes git at most once per working directory. Only default-exec
63
+ * calls participate: an injected `exec` bypasses the cache entirely.
64
+ * @param cwd - the session working directory.
65
+ * @param exec - git probe; defaults to {@link gitExecSync}.
66
+ * @returns the absolute canonical root to slug.
67
+ */
68
+ export declare function canonicalMemoryRoot(cwd: string, exec?: MemoryGitExec): string;
69
+ /**
70
+ * Resolve the per-workspace private memory directory under a memory home:
71
+ * `<home>/projects/<slug>` where the slug encodes the canonical git root
72
+ * of `cwd` (worktrees and subdirectories of one repo share a directory).
73
+ * @param home - the resolved memory home (the global layer's directory).
74
+ * @param cwd - the session working directory; collapsed via {@link canonicalMemoryRoot}.
75
+ * @param exec - optional git probe (tests); omitted uses the default exec.
76
+ * @returns the workspace memory directory.
77
+ */
78
+ export declare function resolveWorkspaceMemoryDir(home: string, cwd: string, exec?: MemoryGitExec): string;
79
+ /**
80
+ * The workspace path an agent's memories belong to: the session's bound cwd,
81
+ * falling back to the process cwd when the header carries none.
82
+ *
83
+ * This is the *live working copy*, not the memory-bucket identity. Task uses
84
+ * it to find `.claude/agents` inside a worktree; {@link resolveWorkspaceMemoryDir}
85
+ * collapses it onto the git root before slugging.
86
+ * @param agent - the agent whose workspace is needed.
87
+ * @returns the workspace cwd.
88
+ */
89
+ export declare function cwdOf(agent: Agent): string;
90
+ /**
91
+ * Disco the project-scoped memory root for a cwd, or `undefined` when the
92
+ * filesystem service is absent or no project root is reachable.
93
+ * @param ctx - the host context carrying the optional `fs` service.
94
+ * @param cwd - the descendant path whose project root bounds the overlay.
95
+ * @returns the project memory directory, or `undefined` when not discoverable.
96
+ */
97
+ export declare function resolveProjectMemoryRoot(ctx: Context, cwd: string): Promise<string | undefined>;
98
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AAInD,6DAA6D;AAC7D,eAAO,MAAM,kBAAkB,mBAAmB,CAAA;AAElD,qEAAqE;AACrE,eAAO,MAAM,YAAY,aAAa,CAAA;AAEtC;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,MAAM,CAAA;AAEvC,+CAA+C;AAC/C,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,KAAK,mBAAmB,GAAG,SAAS,CAAA;AAErG,yEAAyE;AACzE,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAQjG;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAI7D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAoB/C;AAoCD,4EAA4E;AAC5E,wBAAgB,sBAAsB,IAAI,IAAI,CAE7C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,GAAE,aAA2B,GAAG,MAAM,CAS1F;AAqBD;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,MAAM,CAGjG;AAED;;;;;;;;;GASG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAE1C;AAED;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAO7B"}
package/lib/paths.js ADDED
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Memdir root resolution: the default harness-home memory directory plus an
3
+ * optional per-project `.claude/memory` overlay. All reads go through the
4
+ * optional `ctx.fs` seam so remote backends work unchanged.
5
+ * @module @dsh-cc/memory/paths
6
+ */
7
+ import { spawnSync } from 'node:child_process';
8
+ import { realpathSync } from 'node:fs';
9
+ import { dirname, join, resolve, sep } from 'node:path';
10
+ import { defaultDshHome } from '@deepseek-ai/dsh-home-paths';
11
+ /** Directory label for the project-scoped memory overlay. */
12
+ export const PROJECT_MEMORY_DIR = '.claude/memory';
13
+ /** Directory label grouping the per-workspace memory directories. */
14
+ export const PROJECTS_DIR = 'projects';
15
+ /**
16
+ * Git probe timeout for {@link canonicalMemoryRoot}. Bounded so a hung git
17
+ * (network FS, broken env) cannot stall the synchronous system-prompt
18
+ * `text` callback past the section's first-assembly budget.
19
+ */
20
+ export const GIT_PROBE_TIMEOUT_MS = 500;
21
+ /** The default exec: real `git` via spawnSync with a bounded timeout. */
22
+ export function gitExecSync(argv, cwd) {
23
+ const result = spawnSync('git', [...argv], {
24
+ cwd,
25
+ encoding: 'utf8',
26
+ timeout: GIT_PROBE_TIMEOUT_MS,
27
+ });
28
+ if (result.error !== undefined || result.status !== 0)
29
+ return undefined;
30
+ return { stdout: result.stdout };
31
+ }
32
+ /**
33
+ * Resolve the default (harness-home) memory root. A configured root wins;
34
+ * otherwise the shared harness home.
35
+ * @param configured - an explicit root, or `undefined` for the default.
36
+ * @returns the resolved harness-home memory directory.
37
+ */
38
+ export function resolveMemoryHome(configured) {
39
+ return configured !== undefined && configured.length > 0
40
+ ? configured
41
+ : join(defaultDshHome(), 'memory');
42
+ }
43
+ /**
44
+ * Encode a workspace path as a single filesystem-safe directory name. Ported
45
+ * from upstream `projectKey` (`session-persistence-jsonl/src/format.ts`),
46
+ * minus the `--` wrapper: separators and drive colons collapse to `-`, unsafe
47
+ * code units escape as `~XXXX`, leading dashes strip, and the result
48
+ * truncates to 251 chars (fallback `root`). The input is the canonical git
49
+ * root from {@link canonicalMemoryRoot}, so worktrees of one repo share a
50
+ * slug even though session transcripts still group by raw cwd.
51
+ * @param cwd - the workspace path to encode.
52
+ * @returns the workspace slug.
53
+ */
54
+ export function projectSlug(cwd) {
55
+ if (cwd.length === 0)
56
+ return 'root';
57
+ let readable = '';
58
+ let separatorRun = false;
59
+ for (let i = 0; i < cwd.length; i++) {
60
+ const code = cwd.charCodeAt(i);
61
+ const ch = String.fromCharCode(code);
62
+ if (ch === '/' || ch === '\\' || ch === ':') {
63
+ if (!separatorRun)
64
+ readable += '-';
65
+ separatorRun = true;
66
+ }
67
+ else if (ch !== '~' && /^[A-Za-z0-9._-]$/.test(ch)) {
68
+ readable += ch;
69
+ separatorRun = false;
70
+ }
71
+ else {
72
+ readable += '~' + code.toString(16).toUpperCase().padStart(4, '0');
73
+ separatorRun = false;
74
+ }
75
+ }
76
+ const slug = readable.replace(/^-+/, '') || 'root';
77
+ return slug.slice(0, 251);
78
+ }
79
+ /**
80
+ * True when `commonDir` marks a *linked* worktree whose project root is its
81
+ * directory's parent. The common dir of a standard layout is `<root>/.git`;
82
+ * a linked worktree reports its main checkout's `/.git`, which differs from
83
+ * its own `top/.git`. Anything else (main checkout, a submodule's
84
+ * `<super>/.git/modules/<name>`, bare, exotic GIT_DIR) is not a linked
85
+ * worktree and keeps `resolve(top)` as its project root — so submodules do
86
+ * not collapse onto their superproject.
87
+ */
88
+ function isLinkedWorktree(commonDir, top) {
89
+ if (!commonDir.endsWith(`${sep}.git`))
90
+ return false;
91
+ return resolve(commonDir) !== resolve(join(top, '.git'));
92
+ }
93
+ /**
94
+ * Canonicalise a path: `resolve()` then dereference symlinks via realpath so
95
+ * a repo reached through a symlinked ancestor (e.g. macOS `/var` →
96
+ * `/private/var`) yields the SAME string whether it came from `resolve(cwd)`,
97
+ * git's toplevel, or git's common-dir — git may mix realpath'd and non-
98
+ * realpath'd output for the same directory, which would otherwise split one
99
+ * memory bucket into two. Falls back to `resolve()` if realpath fails (e.g.
100
+ * the path no longer exists).
101
+ */
102
+ function canonical(path) {
103
+ try {
104
+ return realpathSync(path);
105
+ }
106
+ catch {
107
+ return resolve(path);
108
+ }
109
+ }
110
+ /** Module-level memo keyed by `resolve(cwd)` (see {@link canonicalMemoryRoot}). */
111
+ const memoryRootCache = new Map();
112
+ /** Test hook: drop the module-level memo (also safe as a no-op cleanup). */
113
+ export function __clearMemoryRootCache() {
114
+ memoryRootCache.clear();
115
+ }
116
+ /**
117
+ * The canonical git-repo root a cwd's workspace memories belong to.
118
+ *
119
+ * Matches Claude Code auto-memory identity: derived from the git repository
120
+ * so worktrees and subdirectories share one store. A linked worktree
121
+ * collapses onto the main checkout (`dirname(git-common-dir)`); a submodule
122
+ * keeps its own toplevel. Non-git / git failure / timeout degrades to
123
+ * `resolve(cwd)` — today's pre-collapse behavior.
124
+ *
125
+ * Results are memoised per resolved `cwd` so the synchronous system-prompt
126
+ * section probes git at most once per working directory. Only default-exec
127
+ * calls participate: an injected `exec` bypasses the cache entirely.
128
+ * @param cwd - the session working directory.
129
+ * @param exec - git probe; defaults to {@link gitExecSync}.
130
+ * @returns the absolute canonical root to slug.
131
+ */
132
+ export function canonicalMemoryRoot(cwd, exec = gitExecSync) {
133
+ const key = resolve(cwd);
134
+ if (exec === gitExecSync) {
135
+ const cached = memoryRootCache.get(key);
136
+ if (cached !== undefined)
137
+ return cached;
138
+ }
139
+ const root = probeMemoryRoot(cwd, exec);
140
+ if (exec === gitExecSync)
141
+ memoryRootCache.set(key, root);
142
+ return root;
143
+ }
144
+ /**
145
+ * One spawn: `git rev-parse --show-toplevel --git-common-dir` prints the
146
+ * toplevel then the common dir, one per line. A relative common-dir is
147
+ * resolved against the probe cwd.
148
+ */
149
+ function probeMemoryRoot(cwd, exec) {
150
+ const parsed = exec(['rev-parse', '--show-toplevel', '--git-common-dir'], cwd)?.stdout;
151
+ if (parsed === undefined)
152
+ return resolve(cwd);
153
+ const lines = parsed.split('\n').map(line => line.trim()).filter(line => line.length > 0);
154
+ const top = lines[0];
155
+ const commonRaw = lines[1];
156
+ if (top === undefined || top.length === 0 || commonRaw === undefined || commonRaw.length === 0) {
157
+ return resolve(cwd);
158
+ }
159
+ const topAbs = canonical(top);
160
+ const commonDir = canonical(resolve(cwd, commonRaw));
161
+ return isLinkedWorktree(commonDir, topAbs) ? dirname(commonDir) : topAbs;
162
+ }
163
+ /**
164
+ * Resolve the per-workspace private memory directory under a memory home:
165
+ * `<home>/projects/<slug>` where the slug encodes the canonical git root
166
+ * of `cwd` (worktrees and subdirectories of one repo share a directory).
167
+ * @param home - the resolved memory home (the global layer's directory).
168
+ * @param cwd - the session working directory; collapsed via {@link canonicalMemoryRoot}.
169
+ * @param exec - optional git probe (tests); omitted uses the default exec.
170
+ * @returns the workspace memory directory.
171
+ */
172
+ export function resolveWorkspaceMemoryDir(home, cwd, exec) {
173
+ const root = exec === undefined ? canonicalMemoryRoot(cwd) : canonicalMemoryRoot(cwd, exec);
174
+ return join(home, PROJECTS_DIR, projectSlug(root));
175
+ }
176
+ /**
177
+ * The workspace path an agent's memories belong to: the session's bound cwd,
178
+ * falling back to the process cwd when the header carries none.
179
+ *
180
+ * This is the *live working copy*, not the memory-bucket identity. Task uses
181
+ * it to find `.claude/agents` inside a worktree; {@link resolveWorkspaceMemoryDir}
182
+ * collapses it onto the git root before slugging.
183
+ * @param agent - the agent whose workspace is needed.
184
+ * @returns the workspace cwd.
185
+ */
186
+ export function cwdOf(agent) {
187
+ return agent.session.header.cwd ?? process.cwd();
188
+ }
189
+ /**
190
+ * Disco the project-scoped memory root for a cwd, or `undefined` when the
191
+ * filesystem service is absent or no project root is reachable.
192
+ * @param ctx - the host context carrying the optional `fs` service.
193
+ * @param cwd - the descendant path whose project root bounds the overlay.
194
+ * @returns the project memory directory, or `undefined` when not discoverable.
195
+ */
196
+ export async function resolveProjectMemoryRoot(ctx, cwd) {
197
+ const fileSystem = ctx.get('fs');
198
+ if (fileSystem === undefined)
199
+ return undefined;
200
+ const cwdResolved = await readResolve(fileSystem, cwd, ctx);
201
+ const root = await findProjectRoot(cwdResolved, fileSystem);
202
+ if (root === undefined)
203
+ return undefined;
204
+ return join(root, PROJECT_MEMORY_DIR);
205
+ }
206
+ async function readResolve(fs, path, ctx) {
207
+ try {
208
+ return (await fs.resolve(path)).displayPath;
209
+ }
210
+ catch (error) {
211
+ ctx.logger.warn(`memory: failed to resolve cwd ${path}: ${String(error)}`);
212
+ return path;
213
+ }
214
+ }
215
+ async function findProjectRoot(cwd, fs) {
216
+ let current = cwd;
217
+ while (true) {
218
+ if (await pathExists(fs, join(current, '.git')))
219
+ return current;
220
+ const parent = dirname(current);
221
+ if (parent === current)
222
+ return undefined;
223
+ current = parent;
224
+ }
225
+ }
226
+ async function pathExists(fs, path) {
227
+ try {
228
+ const target = await fs.resolve(path);
229
+ return await fs.stat(target) !== undefined;
230
+ }
231
+ catch {
232
+ // A backend may refuse or hide the candidate; keep walking upward.
233
+ return false;
234
+ }
235
+ }
236
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"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"}