@try-works/dsh-memory 0.1.19 → 0.1.20

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 (51) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +585 -585
  3. package/STATUS.md +287 -287
  4. package/cordis.patch.yml +69 -69
  5. package/package.json +1 -1
  6. package/scripts/check-package.mjs +76 -76
  7. package/src/SYSTEM.template.md +311 -311
  8. package/src/capture.js +242 -242
  9. package/src/client/index.js +886 -886
  10. package/src/config.js +57 -57
  11. package/src/consolidate-run.js +132 -132
  12. package/src/consolidate.js +330 -330
  13. package/src/context.js +88 -88
  14. package/src/decisions.js +106 -106
  15. package/src/defaults.js +93 -93
  16. package/src/digest.js +255 -255
  17. package/src/entry.js +179 -179
  18. package/src/eval.js +72 -72
  19. package/src/files.js +60 -60
  20. package/src/fixture.js +56 -56
  21. package/src/forget.js +71 -71
  22. package/src/freshness.js +65 -65
  23. package/src/fts.js +166 -166
  24. package/src/git.js +53 -53
  25. package/src/history.js +226 -226
  26. package/src/host.js +866 -866
  27. package/src/index.js +115 -94
  28. package/src/inject.js +108 -108
  29. package/src/layers.js +178 -178
  30. package/src/ledger.js +19 -19
  31. package/src/map.js +184 -184
  32. package/src/models.js +136 -136
  33. package/src/queue.js +602 -602
  34. package/src/rank.js +31 -31
  35. package/src/recover.js +91 -91
  36. package/src/refresh.js +80 -80
  37. package/src/remember.js +58 -58
  38. package/src/rerank.js +90 -90
  39. package/src/search.js +75 -75
  40. package/src/select.js +57 -57
  41. package/src/session.js +60 -60
  42. package/src/state.js +240 -240
  43. package/src/store.js +128 -128
  44. package/src/summarize.js +195 -195
  45. package/src/system-doc.js +171 -171
  46. package/src/tasks.js +617 -617
  47. package/src/telemetry.js +122 -122
  48. package/src/tools.js +828 -818
  49. package/src/working.js +288 -288
  50. package/src/workspace-key.js +68 -68
  51. package/src/workspace.js +87 -87
@@ -1,68 +1,68 @@
1
- // Stable, portable workspace-key derivation (pure). A workspace key must be
2
- // identical across devices that check out the same memory repo, even though the
3
- // local folder path differs. Resolution order: explicit > git remote URL > basename.
4
-
5
- /** Normalize any git remote spelling to a lowercase https URL with no trailing .git or slash. */
6
- export function normalizeRemoteUrl(url) {
7
- if (typeof url !== 'string' || url.trim() === '') return undefined
8
- let u = url.trim()
9
- if (/^[a-z][a-z0-9+.-]*:\/\//i.test(u)) {
10
- u = u.replace(/^[a-z][a-z0-9+.-]*:\/\//i, 'https://')
11
- u = u.replace(/^https:\/\/[^/@]+@/, 'https://') // strip userinfo
12
- } else if (/^[^/\\:@]+@[^/\\:]+:.+/.test(u)) {
13
- const i = u.indexOf(':')
14
- u = 'https://' + u.slice(0, i).replace(/^[^@]+@/, '') + '/' + u.slice(i + 1)
15
- }
16
- u = u.replace(/\.git$/i, '')
17
- u = u.replace(/\/+$/, '')
18
- return u.toLowerCase()
19
- }
20
-
21
- /**
22
- * Filesystem-safe slug from a display segment.
23
- *
24
- * Keeps Unicode letters and digits, replaces everything else with a hyphen. A Latin-only rule
25
- * (a-z0-9) was actively harmful: any CJK, Cyrillic or Greek topic slugged to an EMPTY string,
26
- * so every such topic collapsed into one file and silently merged unrelated subjects. Keeping
27
- * the script is the only lossless option - and pure-ASCII input is unaffected, so existing
28
- * slugs and file names do not move.
29
- *
30
- * Bounded on code points, not UTF-16 units, so a multi-byte name is never cut mid-character.
31
- */
32
- export function slugify(segment) {
33
- const raw = String(segment ?? '').trim().toLowerCase()
34
- const slug = raw.replace(/[^\p{L}\p{N}]+/gu, '-').replace(/^-+|-+$/g, '')
35
- if (slug === '') return ''
36
- const points = [...slug]
37
- return points.length > 60 ? points.slice(0, 60).join('').replace(/-+$/g, '') : slug
38
- }
39
- /** Reject traversal / empty / oversized keys. */
40
- export function sanitizeKey(key) {
41
- if (typeof key !== 'string') return undefined
42
- const s = slugify(key)
43
- if (!s || s === '.' || s === '..' || s.length > 120) return undefined
44
- return s
45
- }
46
-
47
- /** Derive a key from a normalized remote URL; GitHub drops the host, others keep it. */
48
- export function keyFromRemote(url) {
49
- const n = normalizeRemoteUrl(url)
50
- if (!n) return undefined
51
- try {
52
- const u = new URL(n)
53
- const p = u.pathname.replace(/^\/+|\/+$/g, '')
54
- if (u.hostname === 'github.com') return slugify(p.split('/').join('-'))
55
- return slugify(u.hostname.replace(/^www\./, '') + '-' + p.split('/').join('-'))
56
- } catch {
57
- return undefined
58
- }
59
- }
60
-
61
- /** Full resolution order: explicit -> remote -> basename. */
62
- export function deriveWorkspaceKey({ remote, basename, explicit } = {}) {
63
- if (typeof explicit === 'string' && explicit.trim() !== '') return sanitizeKey(explicit)
64
- const fromRemote = keyFromRemote(remote)
65
- if (fromRemote) return fromRemote
66
- if (typeof basename === 'string' && basename.trim() !== '') return sanitizeKey(basename)
67
- return undefined
68
- }
1
+ // Stable, portable workspace-key derivation (pure). A workspace key must be
2
+ // identical across devices that check out the same memory repo, even though the
3
+ // local folder path differs. Resolution order: explicit > git remote URL > basename.
4
+
5
+ /** Normalize any git remote spelling to a lowercase https URL with no trailing .git or slash. */
6
+ export function normalizeRemoteUrl(url) {
7
+ if (typeof url !== 'string' || url.trim() === '') return undefined
8
+ let u = url.trim()
9
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(u)) {
10
+ u = u.replace(/^[a-z][a-z0-9+.-]*:\/\//i, 'https://')
11
+ u = u.replace(/^https:\/\/[^/@]+@/, 'https://') // strip userinfo
12
+ } else if (/^[^/\\:@]+@[^/\\:]+:.+/.test(u)) {
13
+ const i = u.indexOf(':')
14
+ u = 'https://' + u.slice(0, i).replace(/^[^@]+@/, '') + '/' + u.slice(i + 1)
15
+ }
16
+ u = u.replace(/\.git$/i, '')
17
+ u = u.replace(/\/+$/, '')
18
+ return u.toLowerCase()
19
+ }
20
+
21
+ /**
22
+ * Filesystem-safe slug from a display segment.
23
+ *
24
+ * Keeps Unicode letters and digits, replaces everything else with a hyphen. A Latin-only rule
25
+ * (a-z0-9) was actively harmful: any CJK, Cyrillic or Greek topic slugged to an EMPTY string,
26
+ * so every such topic collapsed into one file and silently merged unrelated subjects. Keeping
27
+ * the script is the only lossless option - and pure-ASCII input is unaffected, so existing
28
+ * slugs and file names do not move.
29
+ *
30
+ * Bounded on code points, not UTF-16 units, so a multi-byte name is never cut mid-character.
31
+ */
32
+ export function slugify(segment) {
33
+ const raw = String(segment ?? '').trim().toLowerCase()
34
+ const slug = raw.replace(/[^\p{L}\p{N}]+/gu, '-').replace(/^-+|-+$/g, '')
35
+ if (slug === '') return ''
36
+ const points = [...slug]
37
+ return points.length > 60 ? points.slice(0, 60).join('').replace(/-+$/g, '') : slug
38
+ }
39
+ /** Reject traversal / empty / oversized keys. */
40
+ export function sanitizeKey(key) {
41
+ if (typeof key !== 'string') return undefined
42
+ const s = slugify(key)
43
+ if (!s || s === '.' || s === '..' || s.length > 120) return undefined
44
+ return s
45
+ }
46
+
47
+ /** Derive a key from a normalized remote URL; GitHub drops the host, others keep it. */
48
+ export function keyFromRemote(url) {
49
+ const n = normalizeRemoteUrl(url)
50
+ if (!n) return undefined
51
+ try {
52
+ const u = new URL(n)
53
+ const p = u.pathname.replace(/^\/+|\/+$/g, '')
54
+ if (u.hostname === 'github.com') return slugify(p.split('/').join('-'))
55
+ return slugify(u.hostname.replace(/^www\./, '') + '-' + p.split('/').join('-'))
56
+ } catch {
57
+ return undefined
58
+ }
59
+ }
60
+
61
+ /** Full resolution order: explicit -> remote -> basename. */
62
+ export function deriveWorkspaceKey({ remote, basename, explicit } = {}) {
63
+ if (typeof explicit === 'string' && explicit.trim() !== '') return sanitizeKey(explicit)
64
+ const fromRemote = keyFromRemote(remote)
65
+ if (fromRemote) return fromRemote
66
+ if (typeof basename === 'string' && basename.trim() !== '') return sanitizeKey(basename)
67
+ return undefined
68
+ }
package/src/workspace.js CHANGED
@@ -1,87 +1,87 @@
1
- // Workspace association: a portable registry (memory.json, committed) maps
2
- // key -> {title, remotes, createdAt}; a device-local map (state/associations.json,
3
- // gitignored) maps key -> {path, workspaceId}. Paths are machine-specific and never
4
- // committed, so the same store reconciles across devices by remote URL or basename.
5
- import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
6
- import { basename, join } from 'node:path'
7
- import { workspacesDir, workspaceDir } from './store.js'
8
- import { deriveWorkspaceKey, sanitizeKey } from './workspace-key.js'
9
-
10
- export function associationsPath(root) { return join(root, 'state', 'associations.json') }
11
-
12
- export function readAssociations(root) {
13
- try { return JSON.parse(readFileSync(associationsPath(root), 'utf8')) }
14
- catch { return {} }
15
- }
16
-
17
- export function writeAssociations(root, value) {
18
- mkdirSync(join(root, 'state'), { recursive: true })
19
- writeFileSync(associationsPath(root), JSON.stringify(value, null, 2) + '\n')
20
- }
21
-
22
- /** Resolution order: explicit > device-local association (by canonical path) > remote > basename. */
23
- export function resolveKeyFor(root, { cwd, remote, basename, explicit } = {}) {
24
- if (explicit) return sanitizeKey(explicit)
25
- if (cwd) {
26
- const assoc = readAssociations(root)
27
- const hit = Object.entries(assoc).find(([, a]) => a.path === cwd)
28
- if (hit) return hit[0]
29
- }
30
- return deriveWorkspaceKey({ remote, basename })
31
- }
32
-
33
- /** Registry + on-disk dirs + device-local associations, merged. */
34
- export function listWorkspaces(store) {
35
- const mem = store.readMemoryJson()
36
- const registry = mem.workspaces ?? {}
37
- const assoc = readAssociations(store.root)
38
- const keys = new Set(Object.keys(registry))
39
- const wdir = workspacesDir(store.root)
40
- if (existsSync(wdir)) {
41
- for (const name of readdirSync(wdir)) {
42
- if (existsSync(join(wdir, name, 'meta.json'))) keys.add(name)
43
- }
44
- }
45
- return [...keys].sort().map((key) => {
46
- const rec = registry[key] ?? {}
47
- return {
48
- key,
49
- title: rec.title ?? key,
50
- remotes: rec.remotes ?? [],
51
- createdAt: rec.createdAt,
52
- path: assoc[key]?.path,
53
- workspaceId: assoc[key]?.workspaceId,
54
- }
55
- })
56
- }
57
-
58
- /** Associate a key (explicit, or derived from remote/basename) and record it durably. */
59
- export function associate(store, { key, title, remote, cwd, workspaceId } = {}) {
60
- const safe = sanitizeKey(key) ?? deriveWorkspaceKey({ remote, basename: cwd ? basename(cwd) : undefined })
61
- if (!safe) throw new Error('cannot derive a workspace key; pass an explicit key')
62
- store.ensureWorkspace(safe, { title: title ?? safe, remotes: remote ? [remote] : [] })
63
-
64
- // portable registry (committed to git)
65
- const mem = store.readMemoryJson()
66
- mem.workspaces = mem.workspaces ?? {}
67
- const prev = mem.workspaces[safe] ?? {}
68
- mem.workspaces[safe] = {
69
- title: title ?? prev.title ?? safe,
70
- remotes: remote ? Array.from(new Set([...(prev.remotes ?? []), remote])) : (prev.remotes ?? []),
71
- createdAt: prev.createdAt ?? new Date().toISOString(),
72
- }
73
- store.writeMemoryJson(mem)
74
-
75
- // keep the per-workspace meta.json in sync with the registry
76
- writeFileSync(join(workspaceDir(store.root, safe), 'meta.json'), JSON.stringify({
77
- key: safe, title: mem.workspaces[safe].title, remotes: mem.workspaces[safe].remotes, createdAt: mem.workspaces[safe].createdAt,
78
- }, null, 2) + '\n')
79
-
80
- // device-local association (gitignored)
81
- if (cwd || workspaceId) {
82
- const assoc = readAssociations(store.root)
83
- assoc[safe] = { ...(assoc[safe] ?? {}), ...(cwd ? { path: cwd } : {}), ...(workspaceId ? { workspaceId } : {}) }
84
- writeAssociations(store.root, assoc)
85
- }
86
- return safe
87
- }
1
+ // Workspace association: a portable registry (memory.json, committed) maps
2
+ // key -> {title, remotes, createdAt}; a device-local map (state/associations.json,
3
+ // gitignored) maps key -> {path, workspaceId}. Paths are machine-specific and never
4
+ // committed, so the same store reconciles across devices by remote URL or basename.
5
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
6
+ import { basename, join } from 'node:path'
7
+ import { workspacesDir, workspaceDir } from './store.js'
8
+ import { deriveWorkspaceKey, sanitizeKey } from './workspace-key.js'
9
+
10
+ export function associationsPath(root) { return join(root, 'state', 'associations.json') }
11
+
12
+ export function readAssociations(root) {
13
+ try { return JSON.parse(readFileSync(associationsPath(root), 'utf8')) }
14
+ catch { return {} }
15
+ }
16
+
17
+ export function writeAssociations(root, value) {
18
+ mkdirSync(join(root, 'state'), { recursive: true })
19
+ writeFileSync(associationsPath(root), JSON.stringify(value, null, 2) + '\n')
20
+ }
21
+
22
+ /** Resolution order: explicit > device-local association (by canonical path) > remote > basename. */
23
+ export function resolveKeyFor(root, { cwd, remote, basename, explicit } = {}) {
24
+ if (explicit) return sanitizeKey(explicit)
25
+ if (cwd) {
26
+ const assoc = readAssociations(root)
27
+ const hit = Object.entries(assoc).find(([, a]) => a.path === cwd)
28
+ if (hit) return hit[0]
29
+ }
30
+ return deriveWorkspaceKey({ remote, basename })
31
+ }
32
+
33
+ /** Registry + on-disk dirs + device-local associations, merged. */
34
+ export function listWorkspaces(store) {
35
+ const mem = store.readMemoryJson()
36
+ const registry = mem.workspaces ?? {}
37
+ const assoc = readAssociations(store.root)
38
+ const keys = new Set(Object.keys(registry))
39
+ const wdir = workspacesDir(store.root)
40
+ if (existsSync(wdir)) {
41
+ for (const name of readdirSync(wdir)) {
42
+ if (existsSync(join(wdir, name, 'meta.json'))) keys.add(name)
43
+ }
44
+ }
45
+ return [...keys].sort().map((key) => {
46
+ const rec = registry[key] ?? {}
47
+ return {
48
+ key,
49
+ title: rec.title ?? key,
50
+ remotes: rec.remotes ?? [],
51
+ createdAt: rec.createdAt,
52
+ path: assoc[key]?.path,
53
+ workspaceId: assoc[key]?.workspaceId,
54
+ }
55
+ })
56
+ }
57
+
58
+ /** Associate a key (explicit, or derived from remote/basename) and record it durably. */
59
+ export function associate(store, { key, title, remote, cwd, workspaceId } = {}) {
60
+ const safe = sanitizeKey(key) ?? deriveWorkspaceKey({ remote, basename: cwd ? basename(cwd) : undefined })
61
+ if (!safe) throw new Error('cannot derive a workspace key; pass an explicit key')
62
+ store.ensureWorkspace(safe, { title: title ?? safe, remotes: remote ? [remote] : [] })
63
+
64
+ // portable registry (committed to git)
65
+ const mem = store.readMemoryJson()
66
+ mem.workspaces = mem.workspaces ?? {}
67
+ const prev = mem.workspaces[safe] ?? {}
68
+ mem.workspaces[safe] = {
69
+ title: title ?? prev.title ?? safe,
70
+ remotes: remote ? Array.from(new Set([...(prev.remotes ?? []), remote])) : (prev.remotes ?? []),
71
+ createdAt: prev.createdAt ?? new Date().toISOString(),
72
+ }
73
+ store.writeMemoryJson(mem)
74
+
75
+ // keep the per-workspace meta.json in sync with the registry
76
+ writeFileSync(join(workspaceDir(store.root, safe), 'meta.json'), JSON.stringify({
77
+ key: safe, title: mem.workspaces[safe].title, remotes: mem.workspaces[safe].remotes, createdAt: mem.workspaces[safe].createdAt,
78
+ }, null, 2) + '\n')
79
+
80
+ // device-local association (gitignored)
81
+ if (cwd || workspaceId) {
82
+ const assoc = readAssociations(store.root)
83
+ assoc[safe] = { ...(assoc[safe] ?? {}), ...(cwd ? { path: cwd } : {}), ...(workspaceId ? { workspaceId } : {}) }
84
+ writeAssociations(store.root, assoc)
85
+ }
86
+ return safe
87
+ }