engineering-memory 0.1.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 (40) hide show
  1. package/bin/engineering-memory.mjs +120 -0
  2. package/dispatcher/managed-section.mjs +59 -0
  3. package/dispatcher/sections.mjs +14 -0
  4. package/install/api-url.mjs +39 -0
  5. package/install/cli.mjs +93 -0
  6. package/install/commands.mjs +140 -0
  7. package/install/files.mjs +416 -0
  8. package/install/git-hook.mjs +270 -0
  9. package/install/installer.mjs +279 -0
  10. package/install/mcp-registration.mjs +457 -0
  11. package/package.json +28 -0
  12. package/runtime/dist/src/auth/browser-auth.js +184 -0
  13. package/runtime/dist/src/auth/credential-store.js +181 -0
  14. package/runtime/dist/src/cache/etag-cache.js +123 -0
  15. package/runtime/dist/src/config.js +59 -0
  16. package/runtime/dist/src/git/git-inspector.js +375 -0
  17. package/runtime/dist/src/git/pre-commit.js +44 -0
  18. package/runtime/dist/src/git/verification-gate.js +221 -0
  19. package/runtime/dist/src/index.js +60 -0
  20. package/runtime/dist/src/journal/journal-store.js +1300 -0
  21. package/runtime/dist/src/mcp/server.js +11 -0
  22. package/runtime/dist/src/mcp/tool-definitions.js +405 -0
  23. package/runtime/dist/src/project/repository.js +79 -0
  24. package/runtime/dist/src/runtime/active-context-store.js +356 -0
  25. package/runtime/dist/src/runtime/api-client.js +229 -0
  26. package/runtime/dist/src/runtime/bridge-service.js +2226 -0
  27. package/runtime/dist/src/runtime/offline-outbox.js +274 -0
  28. package/runtime/dist/src/runtime/principal-state.js +97 -0
  29. package/runtime/dist/src/types.js +2 -0
  30. package/runtime/dist/src/utilities/files.js +189 -0
  31. package/runtime/dist/src/utilities/hash.js +19 -0
  32. package/runtime/dist/src/utilities/process.js +32 -0
  33. package/runtime/package-lock.json +137 -0
  34. package/runtime/package.json +32 -0
  35. package/skill/SKILL.md +29 -0
  36. package/skill/agents/openai.yaml +6 -0
  37. package/skill/references/lifecycle.md +102 -0
  38. package/skill/references/memory-updates.md +25 -0
  39. package/skill/references/questionnaires.md +98 -0
  40. package/skill/references/scaffolding.md +38 -0
@@ -0,0 +1,274 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { readdir } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { assertManagedPath, ensureManagedDirectory, readJson, removeFile, writeJson, } from '../utilities/files.js';
5
+ import { sha256, stableStringify } from '../utilities/hash.js';
6
+ const sensitiveKeyPattern = /(authorization|password|passcode|secret|token|cookie|email|phone|mobile|customer|account|user.?id|user.?name)/i;
7
+ const emailPattern = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i;
8
+ const phonePattern = /\+\d[\d ()-]{7,}\d/;
9
+ const bearerPattern = /\bbearer\s+[a-z0-9._~-]+/i;
10
+ const jwtPattern = /\beyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\b/;
11
+ const privateKeyPattern = /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/;
12
+ const credentialUrlPattern = /\b[a-z][a-z0-9+.-]*:\/\/[^\s/@:]+:[^\s/@]+@/i;
13
+ const ipv4Pattern = /\b(?:\d{1,3}\.){3}\d{1,3}\b/;
14
+ const personalHomePathPattern = /(?:^|[\s"'(])(?:[A-Z]:[\\/]+Users[\\/]+[^\\/\s"']+(?:[\\/]|(?=$|\s))|\/(?:Users|home)\/[^/\s"']+(?:\/|(?=$|\s)))/i;
15
+ export class OfflineOutbox {
16
+ root;
17
+ queues = new Map();
18
+ constructor(stateRoot) {
19
+ this.root = join(stateRoot, 'outbox');
20
+ }
21
+ async enqueue(input) {
22
+ assertSafeToPersist(input.body);
23
+ if (input.journalRef) {
24
+ assertSafeToPersist(JSON.parse(JSON.stringify(input.journalRef)));
25
+ }
26
+ const idempotencyKey = input.idempotencyKey ?? randomUUID();
27
+ const id = sha256(`${input.operation}\n${idempotencyKey}`);
28
+ return await this.exclusive(id, async () => {
29
+ const path = this.pathFor(id);
30
+ const existing = await readJson(path, this.root);
31
+ if (existing) {
32
+ const expected = stableStringify({
33
+ operation: input.operation,
34
+ method: input.method,
35
+ path: input.path,
36
+ body: input.body,
37
+ idempotencyKey,
38
+ journalRef: input.journalRef ?? null,
39
+ });
40
+ const actual = stableStringify({
41
+ operation: existing.operation,
42
+ method: existing.method,
43
+ path: existing.path,
44
+ body: existing.body,
45
+ idempotencyKey: existing.idempotencyKey,
46
+ journalRef: existing.journalRef ?? null,
47
+ });
48
+ if (actual !== expected) {
49
+ throw new Error('Outbox idempotency key was reused with different content');
50
+ }
51
+ return existing;
52
+ }
53
+ const entry = {
54
+ id,
55
+ operation: input.operation,
56
+ method: input.method,
57
+ path: input.path,
58
+ body: input.body,
59
+ idempotencyKey,
60
+ createdAt: new Date().toISOString(),
61
+ attempts: 0,
62
+ lastError: null,
63
+ ...(input.journalRef ? { journalRef: input.journalRef } : {}),
64
+ };
65
+ await writeJson(path, entry, this.root);
66
+ return entry;
67
+ });
68
+ }
69
+ async list() {
70
+ await ensureManagedDirectory(this.root, this.root);
71
+ const entries = await readdir(this.root, { withFileTypes: true });
72
+ const files = entries.filter((entry) => entry.name.endsWith('.json'));
73
+ for (const entry of files) {
74
+ if (!entry.isFile() || entry.isSymbolicLink()) {
75
+ throw new Error(`Unsafe offline outbox entry: ${entry.name}`);
76
+ }
77
+ }
78
+ const values = await Promise.all(files.map(async (entry) => {
79
+ const path = await assertManagedPath(this.root, join(this.root, entry.name), false);
80
+ return await readJson(path, this.root);
81
+ }));
82
+ return values.filter((entry) => entry !== null).sort(compareEntries);
83
+ }
84
+ async markAttempt(id, errorKind) {
85
+ await this.exclusive(id, async () => {
86
+ const path = this.pathFor(id);
87
+ const entry = await readJson(path, this.root);
88
+ if (!entry) {
89
+ return;
90
+ }
91
+ entry.attempts += 1;
92
+ entry.lastError = safeErrorKind(errorKind);
93
+ await writeJson(path, entry, this.root);
94
+ });
95
+ }
96
+ async get(id) {
97
+ return await this.exclusive(id, async () => {
98
+ return await readJson(this.pathFor(id), this.root);
99
+ });
100
+ }
101
+ async replaceBody(id, body) {
102
+ assertSafeToPersist(body);
103
+ return await this.exclusive(id, async () => {
104
+ const path = this.pathFor(id);
105
+ const entry = await readJson(path, this.root);
106
+ if (!entry) {
107
+ throw new Error('Offline outbox entry could not be found');
108
+ }
109
+ entry.body = body;
110
+ entry.attempts = 0;
111
+ entry.lastError = null;
112
+ await writeJson(path, entry, this.root);
113
+ return entry;
114
+ });
115
+ }
116
+ async acknowledge(id) {
117
+ await this.exclusive(id, async () => {
118
+ await removeFile(this.pathFor(id), this.root);
119
+ });
120
+ }
121
+ pathFor(id) {
122
+ if (!/^[0-9a-f]{64}$/.test(id)) {
123
+ throw new Error('Invalid offline outbox identifier');
124
+ }
125
+ return join(this.root, `${id}.json`);
126
+ }
127
+ async exclusive(key, action) {
128
+ const previous = this.queues.get(key) ?? Promise.resolve();
129
+ let release = () => undefined;
130
+ const current = new Promise((resolvePromise) => {
131
+ release = resolvePromise;
132
+ });
133
+ const tail = previous.then(() => current);
134
+ this.queues.set(key, tail);
135
+ await previous;
136
+ try {
137
+ return await action();
138
+ }
139
+ finally {
140
+ release();
141
+ if (this.queues.get(key) === tail) {
142
+ this.queues.delete(key);
143
+ }
144
+ }
145
+ }
146
+ }
147
+ export function assertSafeToPersist(value, key = '') {
148
+ if (key && sensitiveKeyPattern.test(key)) {
149
+ throw new Error(`Sensitive field cannot be persisted: ${key}`);
150
+ }
151
+ if (typeof value === 'string') {
152
+ if (emailPattern.test(value) ||
153
+ phonePattern.test(value) ||
154
+ bearerPattern.test(value) ||
155
+ jwtPattern.test(value) ||
156
+ privateKeyPattern.test(value) ||
157
+ credentialUrlPattern.test(value) ||
158
+ ipv4Pattern.test(value) ||
159
+ personalHomePathPattern.test(value)) {
160
+ throw new Error('PII or credentials cannot be persisted');
161
+ }
162
+ return;
163
+ }
164
+ if (Array.isArray(value)) {
165
+ value.forEach((item) => assertSafeToPersist(item, key));
166
+ return;
167
+ }
168
+ if (value !== null && typeof value === 'object') {
169
+ Object.entries(value).forEach(([childKey, item]) => assertSafeToPersist(item, childKey));
170
+ }
171
+ }
172
+ export function normalizeRepositoryPaths(value, repoRoot) {
173
+ const root = trimTrailingSeparators(repoRoot);
174
+ if (!root) {
175
+ throw new Error('Repository root is required for persistent payload normalization');
176
+ }
177
+ return normalizeRepositoryPathValue(value, root);
178
+ }
179
+ export function isSafeToPersist(value) {
180
+ try {
181
+ assertSafeToPersist(value);
182
+ return true;
183
+ }
184
+ catch {
185
+ return false;
186
+ }
187
+ }
188
+ export function redactForCache(value) {
189
+ try {
190
+ return redactValue(value);
191
+ }
192
+ catch {
193
+ return null;
194
+ }
195
+ }
196
+ function redactValue(value) {
197
+ if (typeof value === 'string') {
198
+ assertSafeToPersist(value);
199
+ return value;
200
+ }
201
+ if (Array.isArray(value)) {
202
+ return value.map((entry) => redactValue(entry));
203
+ }
204
+ if (value !== null && typeof value === 'object') {
205
+ const result = {};
206
+ for (const [key, entry] of Object.entries(value)) {
207
+ if (sensitiveKeyPattern.test(key)) {
208
+ continue;
209
+ }
210
+ result[key] = redactValue(entry);
211
+ }
212
+ return result;
213
+ }
214
+ return value;
215
+ }
216
+ function normalizeRepositoryPathValue(value, repoRoot) {
217
+ if (typeof value === 'string') {
218
+ const variants = [
219
+ ...new Set([repoRoot, repoRoot.replace(/\\/g, '/'), repoRoot.replace(/\//g, '\\')]),
220
+ ]
221
+ .filter(Boolean)
222
+ .sort((left, right) => right.length - left.length);
223
+ let normalized = value;
224
+ for (const variant of variants) {
225
+ const flags = /^[A-Za-z]:[\\/]/.test(variant) ? 'gi' : 'g';
226
+ normalized = normalized.replace(new RegExp(`${escapeRegExp(variant)}(?=$|[\\\\/\\s"'])`, flags), '${REPO_ROOT}');
227
+ }
228
+ return normalized.replace(/\$\{REPO_ROOT\}(?:[\\/][^\s"'`,;)]*)?/g, (path) => path.replace(/\\/g, '/'));
229
+ }
230
+ if (Array.isArray(value)) {
231
+ return value.map((entry) => normalizeRepositoryPathValue(entry, repoRoot));
232
+ }
233
+ if (value !== null && typeof value === 'object') {
234
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
235
+ key,
236
+ normalizeRepositoryPathValue(entry, repoRoot),
237
+ ]));
238
+ }
239
+ return value;
240
+ }
241
+ function trimTrailingSeparators(value) {
242
+ const trimmed = value.trim();
243
+ if (/^[A-Za-z]:[\\/]$/.test(trimmed) || trimmed === '/') {
244
+ return trimmed;
245
+ }
246
+ return trimmed.replace(/[\\/]+$/, '');
247
+ }
248
+ function escapeRegExp(value) {
249
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
250
+ }
251
+ function safeErrorKind(value) {
252
+ const normalized = value
253
+ .toLowerCase()
254
+ .replace(/[^a-z0-9._-]+/g, '_')
255
+ .slice(0, 80);
256
+ return normalized || 'unknown_error';
257
+ }
258
+ function compareEntries(left, right) {
259
+ const leftBody = asObject(left.body);
260
+ const rightBody = asObject(right.body);
261
+ if (leftBody?.taskId === rightBody?.taskId &&
262
+ typeof leftBody?.expectedTaskVersion === 'number' &&
263
+ typeof rightBody?.expectedTaskVersion === 'number' &&
264
+ leftBody.expectedTaskVersion !== rightBody.expectedTaskVersion) {
265
+ return leftBody.expectedTaskVersion - rightBody.expectedTaskVersion;
266
+ }
267
+ return left.createdAt === right.createdAt
268
+ ? left.id.localeCompare(right.id)
269
+ : left.createdAt.localeCompare(right.createdAt);
270
+ }
271
+ function asObject(value) {
272
+ return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : null;
273
+ }
274
+ //# sourceMappingURL=offline-outbox.js.map
@@ -0,0 +1,97 @@
1
+ import { join } from 'node:path';
2
+ import { readJson, removeFile, writeJson } from '../utilities/files.js';
3
+ import { sha256 } from '../utilities/hash.js';
4
+ export class PrincipalStateGuard {
5
+ stateRoot;
6
+ credentials;
7
+ cache;
8
+ outbox;
9
+ activeContexts;
10
+ gate;
11
+ ownerPath;
12
+ queue = Promise.resolve();
13
+ constructor(stateRoot, credentials, cache, outbox, activeContexts, gate) {
14
+ this.stateRoot = stateRoot;
15
+ this.credentials = credentials;
16
+ this.cache = cache;
17
+ this.outbox = outbox;
18
+ this.activeContexts = activeContexts;
19
+ this.gate = gate;
20
+ this.ownerPath = join(stateRoot, 'principal-owner.json');
21
+ }
22
+ async ensure() {
23
+ await this.exclusive(async () => {
24
+ const accessToken = await this.credentials.get('access-token');
25
+ if (!accessToken) {
26
+ return;
27
+ }
28
+ const principalHash = principalFingerprint(accessToken);
29
+ const current = await readJson(this.ownerPath, this.stateRoot);
30
+ if (current?.principalHash === principalHash) {
31
+ return;
32
+ }
33
+ if ((await this.outbox.list()).length > 0) {
34
+ throw new Error('Pending offline work belongs to a different authenticated principal');
35
+ }
36
+ await this.clearPrincipalState();
37
+ await writeJson(this.ownerPath, {
38
+ schemaVersion: 1,
39
+ principalHash,
40
+ boundAt: new Date().toISOString(),
41
+ }, this.stateRoot);
42
+ });
43
+ }
44
+ async clearAfterLogout() {
45
+ await this.exclusive(async () => {
46
+ if ((await this.outbox.list()).length > 0) {
47
+ throw new Error('Pending offline work must be resolved before logout');
48
+ }
49
+ await this.clearPrincipalState();
50
+ await this.credentials.clear();
51
+ });
52
+ }
53
+ async clearPrincipalState() {
54
+ await this.cache.clear();
55
+ await this.activeContexts.clear();
56
+ await this.gate.clear();
57
+ await removeFile(this.ownerPath, this.stateRoot);
58
+ }
59
+ async exclusive(action) {
60
+ const previous = this.queue;
61
+ let release = () => undefined;
62
+ const current = new Promise((resolvePromise) => {
63
+ release = resolvePromise;
64
+ });
65
+ this.queue = previous.then(() => current);
66
+ await previous;
67
+ try {
68
+ await action();
69
+ }
70
+ finally {
71
+ release();
72
+ }
73
+ }
74
+ }
75
+ export function principalFingerprint(accessToken) {
76
+ const parts = accessToken.split('.');
77
+ const encodedPayload = parts[1];
78
+ if (parts.length !== 3 || !encodedPayload) {
79
+ throw new Error('Access credential cannot establish a local principal namespace');
80
+ }
81
+ let payload;
82
+ try {
83
+ payload = JSON.parse(Buffer.from(encodedPayload, 'base64url').toString('utf8'));
84
+ }
85
+ catch {
86
+ throw new Error('Access credential cannot establish a local principal namespace');
87
+ }
88
+ if (!payload || typeof payload !== 'object') {
89
+ throw new Error('Access credential cannot establish a local principal namespace');
90
+ }
91
+ const subject = payload.sub;
92
+ if (typeof subject !== 'string') {
93
+ throw new Error('Access credential cannot establish a local principal namespace');
94
+ }
95
+ return sha256(subject);
96
+ }
97
+ //# sourceMappingURL=principal-state.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,189 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { lstat, mkdir, open, readFile, realpath, rename, rm } from 'node:fs/promises';
3
+ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
4
+ export async function atomicWrite(path, content, managedRoot) {
5
+ const root = managedRoot ?? dirname(path);
6
+ await ensureManagedDirectory(root, dirname(path));
7
+ const target = await assertManagedPath(root, path, true);
8
+ await assertWritableTarget(target);
9
+ const temporaryPath = `${target}.${randomUUID()}.tmp`;
10
+ const handle = await open(temporaryPath, 'wx', 0o600);
11
+ let renamed = false;
12
+ try {
13
+ try {
14
+ await handle.writeFile(content);
15
+ await handle.sync();
16
+ }
17
+ finally {
18
+ await handle.close();
19
+ }
20
+ await rename(temporaryPath, target);
21
+ renamed = true;
22
+ await syncDirectory(dirname(target));
23
+ }
24
+ finally {
25
+ if (!renamed) {
26
+ await rm(temporaryPath, { force: true });
27
+ }
28
+ }
29
+ }
30
+ export async function readJson(path, managedRoot) {
31
+ try {
32
+ const target = managedRoot ? await assertManagedPath(managedRoot, path, false) : path;
33
+ return JSON.parse(await readFile(target, 'utf8'));
34
+ }
35
+ catch (error) {
36
+ if (isNodeError(error) && error.code === 'ENOENT') {
37
+ return null;
38
+ }
39
+ throw error;
40
+ }
41
+ }
42
+ export async function writeJson(path, value, managedRoot) {
43
+ await atomicWrite(path, `${JSON.stringify(value, null, 2)}\n`, managedRoot);
44
+ }
45
+ export async function pathExists(path, managedRoot) {
46
+ try {
47
+ const target = managedRoot ? await assertManagedPath(managedRoot, path, false) : path;
48
+ await lstat(target);
49
+ return true;
50
+ }
51
+ catch (error) {
52
+ if (isNodeError(error) && error.code === 'ENOENT') {
53
+ return false;
54
+ }
55
+ throw error;
56
+ }
57
+ }
58
+ export async function removeFile(path, managedRoot) {
59
+ const target = managedRoot ? await assertManagedPath(managedRoot, path, true) : path;
60
+ await rm(target, { force: true });
61
+ }
62
+ export function ensureWithinRoot(root, target) {
63
+ const resolvedRoot = resolve(root);
64
+ const resolvedTarget = resolve(target);
65
+ const pathFromRoot = relative(resolvedRoot, resolvedTarget);
66
+ if (pathFromRoot.startsWith('..') || isAbsolute(pathFromRoot)) {
67
+ throw new Error(`Path is outside the managed root: ${resolvedTarget}`);
68
+ }
69
+ return resolvedTarget;
70
+ }
71
+ export async function canonicalPath(path) {
72
+ try {
73
+ return await realpath(path);
74
+ }
75
+ catch (error) {
76
+ if (!isNodeError(error) || error.code !== 'ENOENT') {
77
+ throw error;
78
+ }
79
+ return resolve(path);
80
+ }
81
+ }
82
+ export async function assertManagedPath(root, target, allowMissingTarget) {
83
+ const resolvedRoot = resolve(root);
84
+ const resolvedTarget = ensureWithinRoot(resolvedRoot, target);
85
+ const rootStat = await lstat(resolvedRoot);
86
+ if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {
87
+ throw new Error(`Managed root must be a real directory: ${resolvedRoot}`);
88
+ }
89
+ const canonicalRoot = await realpath(resolvedRoot);
90
+ let current = resolvedRoot;
91
+ const segments = relative(resolvedRoot, resolvedTarget).split(sep).filter(Boolean);
92
+ for (let index = 0; index < segments.length; index += 1) {
93
+ current = join(current, segments[index]);
94
+ let currentStat;
95
+ try {
96
+ currentStat = await lstat(current);
97
+ }
98
+ catch (error) {
99
+ if (isNodeError(error) && error.code === 'ENOENT' && allowMissingTarget) {
100
+ return resolvedTarget;
101
+ }
102
+ throw error;
103
+ }
104
+ if (currentStat.isSymbolicLink()) {
105
+ throw new Error(`Symbolic links are not allowed in managed paths: ${current}`);
106
+ }
107
+ if (index < segments.length - 1 && !currentStat.isDirectory()) {
108
+ throw new Error(`Managed path parent is not a directory: ${current}`);
109
+ }
110
+ const canonicalCurrent = await realpath(current);
111
+ const fromRoot = relative(canonicalRoot, canonicalCurrent);
112
+ if (fromRoot.startsWith('..') || isAbsolute(fromRoot)) {
113
+ throw new Error(`Managed path escapes its canonical root: ${current}`);
114
+ }
115
+ }
116
+ return resolvedTarget;
117
+ }
118
+ export async function ensureManagedDirectory(root, directory) {
119
+ const resolvedRoot = resolve(root);
120
+ const resolvedDirectory = ensureWithinRoot(resolvedRoot, directory);
121
+ await mkdir(resolvedRoot, { recursive: true });
122
+ const rootStat = await lstat(resolvedRoot);
123
+ if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {
124
+ throw new Error(`Managed root must be a real directory: ${resolvedRoot}`);
125
+ }
126
+ let current = resolvedRoot;
127
+ for (const segment of relative(resolvedRoot, resolvedDirectory).split(sep).filter(Boolean)) {
128
+ current = join(current, segment);
129
+ try {
130
+ const currentStat = await lstat(current);
131
+ if (!currentStat.isDirectory() || currentStat.isSymbolicLink()) {
132
+ throw new Error(`Managed directory contains an unsafe path: ${current}`);
133
+ }
134
+ }
135
+ catch (error) {
136
+ if (!isNodeError(error) || error.code !== 'ENOENT') {
137
+ throw error;
138
+ }
139
+ await mkdir(current, { mode: 0o700 });
140
+ const created = await lstat(current);
141
+ if (!created.isDirectory() || created.isSymbolicLink()) {
142
+ throw new Error(`Managed directory could not be created safely: ${current}`);
143
+ }
144
+ }
145
+ }
146
+ }
147
+ async function assertWritableTarget(path) {
148
+ try {
149
+ const targetStat = await lstat(path);
150
+ if (!targetStat.isFile() || targetStat.isSymbolicLink()) {
151
+ throw new Error(`Managed file target is unsafe: ${path}`);
152
+ }
153
+ }
154
+ catch (error) {
155
+ if (!isNodeError(error) || error.code !== 'ENOENT') {
156
+ throw error;
157
+ }
158
+ }
159
+ }
160
+ async function syncDirectory(path) {
161
+ let handle;
162
+ try {
163
+ handle = await open(path, 'r');
164
+ await handle.sync();
165
+ }
166
+ catch (error) {
167
+ if (!isNodeError(error) ||
168
+ !['EINVAL', 'ENOTSUP', 'EISDIR', 'EPERM', 'EBADF'].includes(error.code ?? '')) {
169
+ throw error;
170
+ }
171
+ }
172
+ finally {
173
+ await handle?.close();
174
+ }
175
+ }
176
+ export function safeSegment(value) {
177
+ const segment = value
178
+ .trim()
179
+ .replace(/[^a-zA-Z0-9._-]+/g, '-')
180
+ .replace(/^-+|-+$/g, '');
181
+ if (!segment || segment === '.' || segment === '..') {
182
+ throw new Error('A safe filesystem segment could not be produced');
183
+ }
184
+ return segment.slice(0, 120);
185
+ }
186
+ export function isNodeError(error) {
187
+ return error instanceof Error && 'code' in error;
188
+ }
189
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1,19 @@
1
+ import { createHash } from 'node:crypto';
2
+ export function sha256(value) {
3
+ return createHash('sha256').update(value).digest('hex');
4
+ }
5
+ export function stableStringify(value) {
6
+ return JSON.stringify(sortValue(value));
7
+ }
8
+ function sortValue(value) {
9
+ if (Array.isArray(value)) {
10
+ return value.map(sortValue);
11
+ }
12
+ if (value !== null && typeof value === 'object') {
13
+ return Object.fromEntries(Object.entries(value)
14
+ .sort(([left], [right]) => left.localeCompare(right))
15
+ .map(([key, item]) => [key, sortValue(item)]));
16
+ }
17
+ return value;
18
+ }
19
+ //# sourceMappingURL=hash.js.map
@@ -0,0 +1,32 @@
1
+ import { spawn } from 'node:child_process';
2
+ export class NativeCommandRunner {
3
+ async run(command, args, options = {}) {
4
+ return await new Promise((resolvePromise, reject) => {
5
+ const child = spawn(command, args, {
6
+ cwd: options.cwd,
7
+ shell: false,
8
+ windowsHide: true,
9
+ stdio: ['pipe', 'pipe', 'pipe'],
10
+ });
11
+ let stdout = '';
12
+ let stderr = '';
13
+ child.stdout.setEncoding('utf8');
14
+ child.stderr.setEncoding('utf8');
15
+ child.stdout.on('data', (chunk) => {
16
+ stdout += chunk;
17
+ });
18
+ child.stderr.on('data', (chunk) => {
19
+ stderr += chunk;
20
+ });
21
+ child.once('error', reject);
22
+ child.once('close', (exitCode) => {
23
+ resolvePromise({ stdout, stderr, exitCode: exitCode ?? -1 });
24
+ });
25
+ if (options.input !== undefined) {
26
+ child.stdin.write(options.input);
27
+ }
28
+ child.stdin.end();
29
+ });
30
+ }
31
+ }
32
+ //# sourceMappingURL=process.js.map