@fml-inc/fml 0.7.0 → 0.8.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +2 -4
- package/README.md +323 -79
- package/agent-assets/fml/SKILL.md +180 -0
- package/bin/fml +4 -18
- package/bin/hook-handler +7 -0
- package/bin/mcp-server +4 -18
- package/bin/observer +6 -0
- package/dist/chunk-3KISRT57.js +140 -0
- package/dist/chunk-56TPYMNK.js +4657 -0
- package/dist/chunk-7V7G7NVF.js +142 -0
- package/dist/chunk-A6HAEVJX.js +1606 -0
- package/dist/chunk-DZKYW327.js +1037 -0
- package/dist/chunk-ENW2GJNO.js +109 -0
- package/dist/{chunk-PZ5AY32C.js → chunk-MLKGABMK.js} +0 -1
- package/dist/{chunk-PTUZMKSN.js → chunk-R45CWWZO.js} +34 -23
- package/dist/chunk-WZLLO563.js +953 -0
- package/dist/chunk-YSU5CS2V.js +3331 -0
- package/dist/cli.js +7742 -1072
- package/dist/{esm-2EUDFZWP.js → esm-4YKLKFRX.js} +546 -741
- package/dist/hook-handler.js +440 -0
- package/dist/mcp/server.js +112 -98
- package/dist/observer-server.js +1566 -0
- package/dist/sync-cycle.js +31 -0
- package/dist/{token-store-3IQSLOTQ.js → token-store-BT5THIAJ.js} +10 -3
- package/hooks/hooks.json +290 -0
- package/package.json +24 -27
- package/dist/chunk-PTUZMKSN.js.map +0 -1
- package/dist/chunk-PZ5AY32C.js.map +0 -1
- package/dist/chunk-S4TJNLEN.js +0 -61
- package/dist/chunk-S4TJNLEN.js.map +0 -1
- package/dist/chunk-VV4JHH4H.js +0 -437
- package/dist/chunk-VV4JHH4H.js.map +0 -1
- package/dist/chunk-YH5ROR7G.js +0 -69
- package/dist/chunk-YH5ROR7G.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/daemon-MSLDHLMH.js +0 -21
- package/dist/daemon-MSLDHLMH.js.map +0 -1
- package/dist/daemon-utils-OODBIYB7.js +0 -10
- package/dist/daemon-utils-OODBIYB7.js.map +0 -1
- package/dist/esm-2EUDFZWP.js.map +0 -1
- package/dist/mcp/server.js.map +0 -1
- package/dist/postinstall.js +0 -16
- package/dist/postinstall.js.map +0 -1
- package/dist/token-store-3IQSLOTQ.js.map +0 -1
- package/scripts/postinstall.cjs +0 -25
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// src/repo.ts
|
|
2
|
+
import { execFile, execFileSync } from "child_process";
|
|
3
|
+
var REPO_CACHE = /* @__PURE__ */ new Map();
|
|
4
|
+
var ASYNC_REPO_CACHE = /* @__PURE__ */ new Map();
|
|
5
|
+
var GIT_TIMEOUT_MS = 5e3;
|
|
6
|
+
var POSITIVE_REPO_CACHE_TTL_MS = 3e4;
|
|
7
|
+
var NEGATIVE_REPO_CACHE_TTL_MS = 1e3;
|
|
8
|
+
var MAX_REPO_CACHE_ENTRIES = 256;
|
|
9
|
+
function cachedRepo(cwd) {
|
|
10
|
+
const entry = REPO_CACHE.get(cwd);
|
|
11
|
+
if (!entry) return { hit: false };
|
|
12
|
+
if (entry.expiresAtMs <= Date.now()) {
|
|
13
|
+
REPO_CACHE.delete(cwd);
|
|
14
|
+
return { hit: false };
|
|
15
|
+
}
|
|
16
|
+
REPO_CACHE.delete(cwd);
|
|
17
|
+
REPO_CACHE.set(cwd, entry);
|
|
18
|
+
return { hit: true, value: entry.value };
|
|
19
|
+
}
|
|
20
|
+
function cacheRepo(cwd, value) {
|
|
21
|
+
REPO_CACHE.delete(cwd);
|
|
22
|
+
while (REPO_CACHE.size >= MAX_REPO_CACHE_ENTRIES) {
|
|
23
|
+
const oldest = REPO_CACHE.keys().next().value;
|
|
24
|
+
if (oldest === void 0) break;
|
|
25
|
+
REPO_CACHE.delete(oldest);
|
|
26
|
+
}
|
|
27
|
+
REPO_CACHE.set(cwd, {
|
|
28
|
+
value,
|
|
29
|
+
expiresAtMs: Date.now() + (value === null ? NEGATIVE_REPO_CACHE_TTL_MS : POSITIVE_REPO_CACHE_TTL_MS)
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function git(cwd, args, timeoutMs) {
|
|
33
|
+
try {
|
|
34
|
+
return execFileSync("git", ["-C", cwd, ...args], {
|
|
35
|
+
encoding: "utf8",
|
|
36
|
+
timeout: timeoutMs,
|
|
37
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
38
|
+
windowsHide: true
|
|
39
|
+
}).trim() || null;
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function gitAsync(cwd, args, signal) {
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
try {
|
|
47
|
+
execFile(
|
|
48
|
+
"git",
|
|
49
|
+
["-C", cwd, ...args],
|
|
50
|
+
{
|
|
51
|
+
encoding: "utf8",
|
|
52
|
+
timeout: GIT_TIMEOUT_MS,
|
|
53
|
+
windowsHide: true,
|
|
54
|
+
...signal ? { signal } : {}
|
|
55
|
+
},
|
|
56
|
+
(error, stdout) => {
|
|
57
|
+
if (signal?.aborted) {
|
|
58
|
+
reject(signal.reason ?? new Error("Repository lookup aborted"));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
resolve(error ? null : stdout.trim() || null);
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (signal?.aborted) {
|
|
66
|
+
reject(signal.reason ?? error);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
resolve(null);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function repositorySlug(remote) {
|
|
74
|
+
const ssh = remote.match(/^[^@]+@[^:]+:([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
75
|
+
if (ssh?.[1]) return ssh[1];
|
|
76
|
+
try {
|
|
77
|
+
const url = new URL(remote);
|
|
78
|
+
const slug = url.pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/, "");
|
|
79
|
+
return /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(slug) ? slug : null;
|
|
80
|
+
} catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function resolveRepoFromCwd(cwd, timeoutMs = GIT_TIMEOUT_MS) {
|
|
85
|
+
const cached = cachedRepo(cwd);
|
|
86
|
+
if (cached.hit) return cached.value;
|
|
87
|
+
const remote = git(cwd, ["remote", "get-url", "origin"], timeoutMs);
|
|
88
|
+
const repo = remote ? repositorySlug(remote) : null;
|
|
89
|
+
const result = repo ? {
|
|
90
|
+
repo,
|
|
91
|
+
branch: git(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], timeoutMs)
|
|
92
|
+
} : null;
|
|
93
|
+
cacheRepo(cwd, result);
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
function resolveRepositorySlugFromCwd(cwd, timeoutMs = GIT_TIMEOUT_MS) {
|
|
97
|
+
const cached = cachedRepo(cwd);
|
|
98
|
+
if (cached.hit) return cached.value?.repo ?? null;
|
|
99
|
+
const remote = git(cwd, ["remote", "get-url", "origin"], timeoutMs);
|
|
100
|
+
return remote ? repositorySlug(remote) : null;
|
|
101
|
+
}
|
|
102
|
+
function resolveRepositoryRootFromCwd(cwd, timeoutMs = GIT_TIMEOUT_MS) {
|
|
103
|
+
return git(cwd, ["rev-parse", "--show-toplevel"], timeoutMs);
|
|
104
|
+
}
|
|
105
|
+
async function resolveRepoFromCwdAsync(cwd, signal) {
|
|
106
|
+
if (signal?.aborted) {
|
|
107
|
+
throw signal.reason ?? new Error("Repository lookup aborted");
|
|
108
|
+
}
|
|
109
|
+
const cached = cachedRepo(cwd);
|
|
110
|
+
if (cached.hit) return cached.value;
|
|
111
|
+
const cachedResolution = ASYNC_REPO_CACHE.get(cwd);
|
|
112
|
+
if (cachedResolution) return await cachedResolution;
|
|
113
|
+
const resolution = (async () => {
|
|
114
|
+
const remote = await gitAsync(cwd, ["remote", "get-url", "origin"], signal);
|
|
115
|
+
const repo = remote ? repositorySlug(remote) : null;
|
|
116
|
+
const result = repo ? {
|
|
117
|
+
repo,
|
|
118
|
+
branch: await gitAsync(
|
|
119
|
+
cwd,
|
|
120
|
+
["rev-parse", "--abbrev-ref", "HEAD"],
|
|
121
|
+
signal
|
|
122
|
+
)
|
|
123
|
+
} : null;
|
|
124
|
+
cacheRepo(cwd, result);
|
|
125
|
+
return result;
|
|
126
|
+
})();
|
|
127
|
+
ASYNC_REPO_CACHE.set(cwd, resolution);
|
|
128
|
+
try {
|
|
129
|
+
return await resolution;
|
|
130
|
+
} finally {
|
|
131
|
+
if (ASYNC_REPO_CACHE.get(cwd) === resolution) {
|
|
132
|
+
ASYNC_REPO_CACHE.delete(cwd);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export {
|
|
138
|
+
resolveRepoFromCwd,
|
|
139
|
+
resolveRepositorySlugFromCwd,
|
|
140
|
+
resolveRepositoryRootFromCwd,
|
|
141
|
+
resolveRepoFromCwdAsync
|
|
142
|
+
};
|