@ddtcorex/dsh-maestro-memory 1.0.1

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 (109) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/LICENSE +21 -0
  3. package/README.md +448 -0
  4. package/cordis.patch.yml +13 -0
  5. package/lib/client.js +756 -0
  6. package/lib/index.d.ts +10 -0
  7. package/lib/index.d.ts.map +1 -0
  8. package/lib/index.js +627 -0
  9. package/lib/index.js.map +1 -0
  10. package/lib/memory/batch.d.ts +44 -0
  11. package/lib/memory/batch.d.ts.map +1 -0
  12. package/lib/memory/batch.js +75 -0
  13. package/lib/memory/batch.js.map +1 -0
  14. package/lib/memory/feedback.d.ts +18 -0
  15. package/lib/memory/feedback.d.ts.map +1 -0
  16. package/lib/memory/feedback.js +29 -0
  17. package/lib/memory/feedback.js.map +1 -0
  18. package/lib/memory/store.d.ts +154 -0
  19. package/lib/memory/store.d.ts.map +1 -0
  20. package/lib/memory/store.js +606 -0
  21. package/lib/memory/store.js.map +1 -0
  22. package/lib/migration/cli.d.ts +18 -0
  23. package/lib/migration/cli.d.ts.map +1 -0
  24. package/lib/migration/cli.js +143 -0
  25. package/lib/migration/cli.js.map +1 -0
  26. package/lib/migration/fixture.d.ts +45 -0
  27. package/lib/migration/fixture.d.ts.map +1 -0
  28. package/lib/migration/fixture.js +177 -0
  29. package/lib/migration/fixture.js.map +1 -0
  30. package/lib/migration/service.d.ts +75 -0
  31. package/lib/migration/service.d.ts.map +1 -0
  32. package/lib/migration/service.js +713 -0
  33. package/lib/migration/service.js.map +1 -0
  34. package/lib/prompt/snapshot.d.ts +26 -0
  35. package/lib/prompt/snapshot.d.ts.map +1 -0
  36. package/lib/prompt/snapshot.js +74 -0
  37. package/lib/prompt/snapshot.js.map +1 -0
  38. package/lib/review/queue.d.ts +72 -0
  39. package/lib/review/queue.d.ts.map +1 -0
  40. package/lib/review/queue.js +306 -0
  41. package/lib/review/queue.js.map +1 -0
  42. package/lib/skills-browser.d.ts +67 -0
  43. package/lib/skills-browser.d.ts.map +1 -0
  44. package/lib/skills-browser.js +185 -0
  45. package/lib/skills-browser.js.map +1 -0
  46. package/lib/storage/atomic-store.d.ts +211 -0
  47. package/lib/storage/atomic-store.d.ts.map +1 -0
  48. package/lib/storage/atomic-store.js +582 -0
  49. package/lib/storage/atomic-store.js.map +1 -0
  50. package/lib/storage/layout.d.ts +81 -0
  51. package/lib/storage/layout.d.ts.map +1 -0
  52. package/lib/storage/layout.js +162 -0
  53. package/lib/storage/layout.js.map +1 -0
  54. package/lib/storage/legacy-format.d.ts +104 -0
  55. package/lib/storage/legacy-format.d.ts.map +1 -0
  56. package/lib/storage/legacy-format.js +262 -0
  57. package/lib/storage/legacy-format.js.map +1 -0
  58. package/lib/sync/config.d.ts +22 -0
  59. package/lib/sync/config.d.ts.map +1 -0
  60. package/lib/sync/config.js +57 -0
  61. package/lib/sync/config.js.map +1 -0
  62. package/lib/sync/git.d.ts +97 -0
  63. package/lib/sync/git.d.ts.map +1 -0
  64. package/lib/sync/git.js +282 -0
  65. package/lib/sync/git.js.map +1 -0
  66. package/lib/sync/index.d.ts +6 -0
  67. package/lib/sync/index.d.ts.map +1 -0
  68. package/lib/sync/index.js +6 -0
  69. package/lib/sync/index.js.map +1 -0
  70. package/lib/sync/layout.d.ts +15 -0
  71. package/lib/sync/layout.d.ts.map +1 -0
  72. package/lib/sync/layout.js +33 -0
  73. package/lib/sync/layout.js.map +1 -0
  74. package/lib/sync/merge.d.ts +42 -0
  75. package/lib/sync/merge.d.ts.map +1 -0
  76. package/lib/sync/merge.js +159 -0
  77. package/lib/sync/merge.js.map +1 -0
  78. package/lib/sync/service.d.ts +70 -0
  79. package/lib/sync/service.d.ts.map +1 -0
  80. package/lib/sync/service.js +508 -0
  81. package/lib/sync/service.js.map +1 -0
  82. package/lib/todo/store.d.ts +142 -0
  83. package/lib/todo/store.d.ts.map +1 -0
  84. package/lib/todo/store.js +452 -0
  85. package/lib/todo/store.js.map +1 -0
  86. package/lib/types/client/index.d.ts +7 -0
  87. package/lib/types/client/index.d.ts.map +1 -0
  88. package/package.json +63 -0
  89. package/src/client/index.tsx +994 -0
  90. package/src/host/index.ts +591 -0
  91. package/src/host/memory/batch.ts +107 -0
  92. package/src/host/memory/feedback.ts +36 -0
  93. package/src/host/memory/store.ts +650 -0
  94. package/src/host/migration/cli.ts +139 -0
  95. package/src/host/migration/fixture.ts +218 -0
  96. package/src/host/migration/service.ts +786 -0
  97. package/src/host/prompt/snapshot.ts +95 -0
  98. package/src/host/review/queue.ts +325 -0
  99. package/src/host/skills-browser.ts +191 -0
  100. package/src/host/storage/atomic-store.ts +605 -0
  101. package/src/host/storage/layout.ts +191 -0
  102. package/src/host/storage/legacy-format.ts +308 -0
  103. package/src/host/sync/config.ts +67 -0
  104. package/src/host/sync/git.ts +280 -0
  105. package/src/host/sync/index.ts +5 -0
  106. package/src/host/sync/layout.ts +39 -0
  107. package/src/host/sync/merge.ts +188 -0
  108. package/src/host/sync/service.ts +494 -0
  109. package/src/host/todo/store.ts +480 -0
@@ -0,0 +1,185 @@
1
+ /**
2
+ * host/skills-browser.ts — M6 read-first skills browser
3
+ *
4
+ * Goal: expose existing Maestro skills without taking ownership.
5
+ * - Confirm boundary with maestro-skills and list metadata/origin only.
6
+ * - Read-only: no mutation, no write API.
7
+ * - Model suggestions cannot change skills.
8
+ * - Browser cannot alter maestro-skills discovery behavior (no provider registration, no writes).
9
+ *
10
+ * If later approved to edit, mutation must require explicit user action and
11
+ * symlink/path containment tests before any write support. This file prepares
12
+ * the containment helper (isPathContained) and documents the guard, but does
13
+ * not implement mutation in M6.
14
+ *
15
+ * NOTE for future mutation (NOT implemented in M6):
16
+ * Any future mutation endpoint must require an explicit user action
17
+ * (UI click with confirmation) and must verify path containment via
18
+ * isPathContained(targetPath, allowedRoot) including realpath resolution
19
+ * for symlinks, rejecting any traversal or symlink escape. Tests for
20
+ * symlink/path containment must pass before mutation is enabled.
21
+ */
22
+ import { existsSync, readdirSync, statSync, lstatSync, readFileSync } from 'node:fs';
23
+ import { join, resolve, relative } from 'node:path';
24
+ /**
25
+ * Whether child is contained inside parent (no traversal escape).
26
+ * Uses resolved absolute paths and checks relative path does not start with "..".
27
+ * This helper prepares symlink/path containment tests required before any
28
+ * future mutation support — explicit user action + containment check.
29
+ */
30
+ export function isPathContained(child, parent) {
31
+ const resolvedChild = resolve(child);
32
+ const resolvedParent = resolve(parent);
33
+ if (resolvedChild === resolvedParent)
34
+ return true;
35
+ const rel = relative(resolvedParent, resolvedChild);
36
+ // contained if rel does not start with ".." and is not absolute
37
+ return rel !== '' && !rel.startsWith('..') && !rel.startsWith('/');
38
+ }
39
+ function parseFrontmatter(rawContent) {
40
+ if (!rawContent.startsWith('---'))
41
+ return { metadata: {}, body: rawContent };
42
+ const endIdx = rawContent.indexOf('\n---', 3);
43
+ if (endIdx === -1)
44
+ return { metadata: {}, body: rawContent };
45
+ const yamlLines = rawContent.slice(3, endIdx).split('\n');
46
+ const body = rawContent.slice(endIdx + 4).trim();
47
+ const metadata = {};
48
+ let blockKey;
49
+ for (const line of yamlLines) {
50
+ const trimmed = line.trim();
51
+ const indent = line.length - trimmed.length;
52
+ if (blockKey !== undefined) {
53
+ if (trimmed === '') {
54
+ blockKey = undefined;
55
+ continue;
56
+ }
57
+ if (indent > 0) {
58
+ metadata[blockKey] = (metadata[blockKey] + ' ' + trimmed).replace(/\s+/g, ' ').trim();
59
+ continue;
60
+ }
61
+ blockKey = undefined;
62
+ }
63
+ const colonIdx = trimmed.indexOf(':');
64
+ if (colonIdx <= 0)
65
+ continue;
66
+ const key = trimmed.slice(0, colonIdx).trim();
67
+ const val = trimmed.slice(colonIdx + 1).trim();
68
+ if (val.startsWith('|') || val.startsWith('>')) {
69
+ metadata[key] = '';
70
+ blockKey = key;
71
+ continue;
72
+ }
73
+ metadata[key] = val.startsWith('"') && val.endsWith('"') ? val.slice(1, -1) : val;
74
+ }
75
+ return { metadata, body };
76
+ }
77
+ /**
78
+ * List skills from a single directory, read-only.
79
+ * - Scans <skillsDir>/* /SKILL.md
80
+ * - Parses frontmatter for name/description
81
+ * - Returns metadata/origin only, does not return body/content
82
+ * - Does not follow symlink escapes for future mutation; for listing we
83
+ * report the logical path, but future mutation will verify containment via
84
+ * isPathContained + realpath.
85
+ */
86
+ export function listSkillsSync(skillsDir, origin = 'custom') {
87
+ if (!skillsDir)
88
+ return [];
89
+ let entries = [];
90
+ try {
91
+ entries = readdirSync(skillsDir);
92
+ }
93
+ catch {
94
+ return [];
95
+ }
96
+ const out = [];
97
+ for (const entry of entries) {
98
+ const skillFolder = join(skillsDir, entry);
99
+ let st = null;
100
+ try {
101
+ st = lstatSync(skillFolder);
102
+ }
103
+ catch {
104
+ continue;
105
+ }
106
+ // allow directory or symlink to directory
107
+ const isDir = st.isDirectory() || st.isSymbolicLink();
108
+ if (!isDir)
109
+ continue;
110
+ // verify it is a directory (follow symlink)
111
+ try {
112
+ const follow = statSync(skillFolder);
113
+ if (!follow.isDirectory())
114
+ continue;
115
+ }
116
+ catch {
117
+ continue;
118
+ }
119
+ const skillFilePath = join(skillFolder, 'SKILL.md');
120
+ try {
121
+ const fileSt = statSync(skillFilePath);
122
+ if (!fileSt.isFile())
123
+ continue;
124
+ }
125
+ catch {
126
+ continue;
127
+ }
128
+ let rawContent = '';
129
+ try {
130
+ rawContent = readFileSync(skillFilePath, 'utf8');
131
+ }
132
+ catch {
133
+ continue;
134
+ }
135
+ const { metadata } = parseFrontmatter(rawContent);
136
+ const skillName = metadata.name || entry;
137
+ const description = metadata.description || `Skill for ${skillName}`;
138
+ out.push({
139
+ name: skillName,
140
+ description,
141
+ origin,
142
+ path: skillFilePath,
143
+ locator: skillFilePath,
144
+ metadata,
145
+ });
146
+ }
147
+ return out;
148
+ }
149
+ /**
150
+ * Async variant aggregating multiple roots.
151
+ * Each root is { dir, origin }. If only skillsDir is supplied, use that.
152
+ */
153
+ export async function listSkills(opts = {}) {
154
+ if (opts.roots && opts.roots.length > 0) {
155
+ const all = [];
156
+ for (const r of opts.roots) {
157
+ const entries = listSkillsSync(r.dir, r.origin);
158
+ all.push(...entries);
159
+ }
160
+ return all;
161
+ }
162
+ if (opts.skillsDir) {
163
+ return listSkillsSync(opts.skillsDir, opts.origin ?? 'custom');
164
+ }
165
+ // default: no dir => empty (caller should supply maestro-skills dir explicitly)
166
+ return [];
167
+ }
168
+ /**
169
+ * Resolve default maestro-skills directory for browser (read-only).
170
+ * Tries well-known checkout location; returns null if not present.
171
+ * This does NOT alter maestro-skills discovery — it only reads the directory
172
+ * if it exists, for metadata/origin listing.
173
+ */
174
+ export function resolveDefaultMaestroSkillsDir() {
175
+ const candidates = [
176
+ '/home/kai/Work/htdocs/maestro-harness/maestro-skills/skills',
177
+ join(process.cwd(), '../maestro-skills/skills'),
178
+ ];
179
+ for (const c of candidates) {
180
+ if (existsSync(c))
181
+ return c;
182
+ }
183
+ return null;
184
+ }
185
+ //# sourceMappingURL=skills-browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills-browser.js","sourceRoot":"","sources":["../src/host/skills-browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACpF,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAiBnD;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,KAAa,EAAE,MAAc;IAC3D,MAAM,aAAa,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;IACpC,MAAM,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACtC,IAAI,aAAa,KAAK,cAAc;QAAE,OAAO,IAAI,CAAA;IACjD,MAAM,GAAG,GAAG,QAAQ,CAAC,cAAc,EAAE,aAAa,CAAC,CAAA;IACnD,gEAAgE;IAChE,OAAO,GAAG,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAA;AACpE,CAAC;AAED,SAAS,gBAAgB,CAAC,UAAkB;IAC1C,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAA;IAC5E,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;IAC7C,IAAI,MAAM,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAA;IAC5D,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACzD,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;IAChD,MAAM,QAAQ,GAA2B,EAAE,CAAA;IAC3C,IAAI,QAA4B,CAAA;IAChC,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;QAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;QAC3C,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;gBACnB,QAAQ,GAAG,SAAS,CAAA;gBACpB,SAAQ;YACV,CAAC;YACD,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;gBACf,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,GAAG,GAAG,OAAO,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;gBACrF,SAAQ;YACV,CAAC;YACD,QAAQ,GAAG,SAAS,CAAA;QACtB,CAAC;QACD,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;QACrC,IAAI,QAAQ,IAAI,CAAC;YAAE,SAAQ;QAC3B,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAA;QAC7C,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;QAC9C,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/C,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,CAAA;YAClB,QAAQ,GAAG,GAAG,CAAA;YACd,SAAQ;QACV,CAAC;QACD,QAAQ,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;IACnF,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;AAC3B,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,SAAiB,EAAE,MAAM,GAAG,QAAQ;IACjE,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,CAAA;IACzB,IAAI,OAAO,GAAa,EAAE,CAAA;IAC1B,IAAI,CAAC;QACH,OAAO,GAAG,WAAW,CAAC,SAAS,CAAC,CAAA;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAA;IACX,CAAC;IACD,MAAM,GAAG,GAAuB,EAAE,CAAA;IAClC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAA;QAC1C,IAAI,EAAE,GAAQ,IAAI,CAAA;QAClB,IAAI,CAAC;YACH,EAAE,GAAG,SAAS,CAAC,WAAW,CAAC,CAAA;QAC7B,CAAC;QAAC,MAAM,CAAC;YACP,SAAQ;QACV,CAAC;QACD,0CAA0C;QAC1C,MAAM,KAAK,GAAG,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,cAAc,EAAE,CAAA;QACrD,IAAI,CAAC,KAAK;YAAE,SAAQ;QACpB,4CAA4C;QAC5C,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAA;YACpC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE;gBAAE,SAAQ;QACrC,CAAC;QAAC,MAAM,CAAC;YACP,SAAQ;QACV,CAAC;QACD,MAAM,aAAa,GAAG,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,CAAA;QACnD,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAA;YACtC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE;gBAAE,SAAQ;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,SAAQ;QACV,CAAC;QACD,IAAI,UAAU,GAAG,EAAE,CAAA;QACnB,IAAI,CAAC;YACH,UAAU,GAAG,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAA;QAClD,CAAC;QAAC,MAAM,CAAC;YACP,SAAQ;QACV,CAAC;QACD,MAAM,EAAE,QAAQ,EAAE,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAA;QACjD,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,IAAI,KAAK,CAAA;QACxC,MAAM,WAAW,GAAG,QAAQ,CAAC,WAAW,IAAI,aAAa,SAAS,EAAE,CAAA;QACpE,GAAG,CAAC,IAAI,CAAC;YACP,IAAI,EAAE,SAAS;YACf,WAAW;YACX,MAAM;YACN,IAAI,EAAE,aAAa;YACnB,OAAO,EAAE,aAAa;YACtB,QAAQ;SACT,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,OAAuB,EAAE;IACxD,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxC,MAAM,GAAG,GAAuB,EAAE,CAAA;QAClC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC3B,MAAM,OAAO,GAAG,cAAc,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,CAAA;YAC/C,GAAG,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAA;QACtB,CAAC;QACD,OAAO,GAAG,CAAA;IACZ,CAAC;IACD,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QACnB,OAAO,cAAc,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,IAAI,QAAQ,CAAC,CAAA;IAChE,CAAC;IACD,gFAAgF;IAChF,OAAO,EAAE,CAAA;AACX,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,8BAA8B;IAC5C,MAAM,UAAU,GAAG;QACjB,6DAA6D;QAC7D,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,0BAA0B,CAAC;KAChD,CAAA;IACD,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,IAAI,UAAU,CAAC,CAAC,CAAC;YAAE,OAAO,CAAC,CAAA;IAC7B,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC"}
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Atomic file store for Maestro memory — per-directory lock, temp-write/rename,
3
+ * reread validation, backups, duplicate detection.
4
+ *
5
+ * Each mutation:
6
+ * 1. Acquires a same-directory lock file (.maestro.lock) with stale detection
7
+ * (mtime timeout + pid liveness via kill(pid,0)).
8
+ * 2. Validates the on-disk content round-trips through parseEntries/serializeEntries.
9
+ * Non-canonical files are backed up to `<file>.bak.<timestamp>` before the
10
+ * operation is refused (drift guard).
11
+ * 3. Checks for exact duplicates (ID-stripped) so re-adds are no-ops.
12
+ * 4. Writes to a same-directory temp file (`.<uuid>.tmp`, mode 0o600, flag wx),
13
+ * fsyncs the temp, renames atomically over the target, fsyncs the parent
14
+ * directory on POSIX, then rereads and validates the result.
15
+ * 5. Cleans up the temp file on any failure; the previous file is never
16
+ * clobbered on error.
17
+ *
18
+ * Uses only `node:fs` / `node:fs/promises` atomic primitives: open(wx),
19
+ * writeFile, fsync, rename, unlink. No third-party dependencies.
20
+ *
21
+ * @module storage/atomic-store
22
+ */
23
+ /** Entry delimiter, byte-compatible with Hermes MEMORY.md / USER.md. */
24
+ export declare const ENTRY_DELIMITER = "\n\u00A7\n";
25
+ /**
26
+ * Split raw file text into trimmed, non-empty entries.
27
+ * @param text - raw file content
28
+ * @returns entries
29
+ */
30
+ export declare function parseEntries(text: string): string[];
31
+ /**
32
+ * Serialize entries into canonical file text.
33
+ * @param entries - entries to serialize
34
+ * @returns canonical file content (entries joined by delimiter + trailing newline)
35
+ */
36
+ export declare function serializeEntries(entries: string[]): string;
37
+ /**
38
+ * Whether raw text is the canonical serialization of its own entries.
39
+ * Blank / whitespace-only text counts as canonical (empty store).
40
+ * @param text - raw file content
41
+ * @returns true when round-tripping preserves the text
42
+ */
43
+ export declare function isCanonical(text: string): boolean;
44
+ /**
45
+ * Strip the `[id:xxxxxxxx]` prefix from an entry, if present.
46
+ * @param entry - full entry text
47
+ * @returns text without the ID prefix
48
+ */
49
+ export declare function stripEntryId(entry: string): string;
50
+ /**
51
+ * Check whether `candidate` already exists in `entries`, comparing after
52
+ * stripping IDs so a re-add with a new random ID is still detected as a
53
+ * duplicate (IDs are random per write, direct includes would never hit).
54
+ * @param entries - existing entries
55
+ * @param candidate - new entry text
56
+ * @returns true when a duplicate exists
57
+ */
58
+ export declare function isDuplicate(entries: string[], candidate: string): boolean;
59
+ /**
60
+ * Find the exact index of `exact` in `entries`, ID-immune (strip-and-compare).
61
+ * Returns -1 when not found or when multiple hits make the match ambiguous.
62
+ * @param entries - existing entries
63
+ * @param exact - full entry text to find
64
+ * @returns index or -1
65
+ */
66
+ export declare function findExactIndex(entries: string[], exact: string): number;
67
+ /** A lock file older than this is considered abandoned (stale). */
68
+ export declare const STALE_LOCK_MS = 10000;
69
+ /** How long to keep waiting for the lock before failing. */
70
+ export declare const LOCK_TIMEOUT_MS = 5000;
71
+ /** Spin interval while waiting for the lock. */
72
+ export declare const LOCK_RETRY_MS = 25;
73
+ /** Lock file name inside each directory. */
74
+ export declare const LOCK_FILE = ".maestro.lock";
75
+ /**
76
+ * Determine whether a lock is stale (exported for tests): mtime timeout or
77
+ * the pid inside the lock file is no longer alive (killed / power loss ->
78
+ * stale immediately without waiting for timeout).
79
+ * @param lockPath - lock file path
80
+ * @returns true when stale
81
+ */
82
+ export declare function isStaleLock(lockPath: string): boolean;
83
+ /** Async variant of stale check for the async lock path. */
84
+ export declare function isStaleLockAsync(lockPath: string): Promise<boolean>;
85
+ /**
86
+ * Acquire the directory lock synchronously, run `fn`, release.
87
+ * Reentrant within this process (outer section remains exclusive vs others).
88
+ * @param dir - directory whose lock to take
89
+ * @param fn - critical section
90
+ * @returns section result
91
+ */
92
+ export declare function withLockSync<T>(dir: string, fn: () => T): T;
93
+ /**
94
+ * Acquire the directory lock asynchronously, run `fn`, release.
95
+ * Reentrant within this process.
96
+ * @param dir - directory whose lock to take
97
+ * @param fn - critical section (may be async)
98
+ * @returns section result
99
+ */
100
+ export declare function withLock<T>(dir: string, fn: () => T | Promise<T>): Promise<T>;
101
+ /**
102
+ * Create a backup of `filePath` at `<file>.bak.<timestamp>`.
103
+ * Returns the backup path, or null when the source does not exist.
104
+ * @param filePath - file to back up
105
+ * @returns backup path or null
106
+ */
107
+ export declare function createBackupSync(filePath: string): string | null;
108
+ /**
109
+ * Async backup.
110
+ * @param filePath - file to back up
111
+ * @returns backup path or null
112
+ */
113
+ export declare function createBackup(filePath: string): Promise<string | null>;
114
+ /**
115
+ * Durably replace `filePath` with `content` via same-directory temp file,
116
+ * fsync, atomic rename, directory fsync, and reread validation.
117
+ * @param filePath - absolute target file path
118
+ * @param content - full new file content
119
+ */
120
+ export declare function writeAtomic(filePath: string, content: string): Promise<void>;
121
+ /**
122
+ * Synchronous atomic write (same guarantees, blocking).
123
+ * @param filePath - absolute target file path
124
+ * @param content - full new file content
125
+ */
126
+ export declare function writeAtomicSync(filePath: string, content: string): void;
127
+ /**
128
+ * Read entries from `filePath`. Missing file yields [].
129
+ * @param filePath - file to read
130
+ * @returns parsed entries
131
+ */
132
+ export declare function readEntriesSync(filePath: string): string[];
133
+ /**
134
+ * Async read entries.
135
+ * @param filePath - file to read
136
+ * @returns parsed entries
137
+ */
138
+ export declare function readEntries(filePath: string): Promise<string[]>;
139
+ /**
140
+ * Append `entry` to `filePath` atomically, with duplicate detection and
141
+ * drift-guard backup.
142
+ *
143
+ * - Acquires per-directory lock.
144
+ * - Validates existing content is canonical; if not, backs up and throws.
145
+ * - Checks for duplicate (ID-stripped); returns `{duplicate:true}` without writing.
146
+ * - Writes via temp+rename+reread.
147
+ *
148
+ * @param filePath - target file
149
+ * @param entry - entry to append (already trimmed)
150
+ * @returns result
151
+ */
152
+ export declare function appendEntryAtomic(filePath: string, entry: string): Promise<{
153
+ ok: true;
154
+ duplicate?: boolean;
155
+ entries: string[];
156
+ } | {
157
+ ok: false;
158
+ error: string;
159
+ backup?: string;
160
+ }>;
161
+ /**
162
+ * Synchronous variant of appendEntryAtomic.
163
+ * @param filePath - target file
164
+ * @param entry - entry to append
165
+ * @returns result
166
+ */
167
+ export declare function appendEntryAtomicSync(filePath: string, entry: string): {
168
+ ok: true;
169
+ duplicate?: boolean;
170
+ entries: string[];
171
+ } | {
172
+ ok: false;
173
+ error: string;
174
+ backup?: string;
175
+ };
176
+ /**
177
+ * Replace or remove via atomic write with drift guard and backup.
178
+ * Caller provides the next entries array; this validates, backs up on drift, and writes.
179
+ * @param filePath - target file
180
+ * @param nextEntries - complete new entries array
181
+ * @returns result
182
+ */
183
+ export declare function writeEntriesAtomic(filePath: string, nextEntries: string[]): Promise<{
184
+ ok: true;
185
+ entries: string[];
186
+ } | {
187
+ ok: false;
188
+ error: string;
189
+ backup?: string;
190
+ }>;
191
+ /**
192
+ * Synchronous variant.
193
+ * @param filePath - target file
194
+ * @param nextEntries - complete new entries array
195
+ * @returns result
196
+ */
197
+ export declare function writeEntriesAtomicSync(filePath: string, nextEntries: string[]): {
198
+ ok: true;
199
+ entries: string[];
200
+ } | {
201
+ ok: false;
202
+ error: string;
203
+ backup?: string;
204
+ };
205
+ /**
206
+ * Stable 12-hex project hash for a cwd.
207
+ * @param cwd - working directory
208
+ * @returns 12-char hash
209
+ */
210
+ export declare function projectHash(cwd: string): string;
211
+ //# sourceMappingURL=atomic-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomic-store.d.ts","sourceRoot":"","sources":["../../src/host/storage/atomic-store.ts"],"names":[],"mappings":"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"}