@cassiomc1/forgeloop 1.10.2 → 1.11.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 (84) hide show
  1. package/.cursor/rules/project-loop.mdc +2 -2
  2. package/.forgeloop/forgeloop.gitignore +1 -0
  3. package/.github/copilot-instructions.md +2 -2
  4. package/AGENTS.md +1 -0
  5. package/AGENT_COMPATIBILITY.md +7 -0
  6. package/CLAUDE.md +1 -0
  7. package/DOCS_INDEX.md +10 -0
  8. package/LOOP_SYSTEM_DESIGN.md +12 -0
  9. package/ORCHESTRATOR_INTEGRATION.md +9 -0
  10. package/PROTOCOL_INTEGRATION.md +17 -0
  11. package/README.md +12 -10
  12. package/THIRD_PARTY_NOTICES.md +11 -0
  13. package/THREAT_MODEL.md +21 -0
  14. package/benchmarks/repository-index/README.md +73 -0
  15. package/benchmarks/repository-index/queries.json +12 -0
  16. package/benchmarks/repository-index/run-hot-path.mjs +142 -0
  17. package/benchmarks/repository-index/run-persistent-transport.mjs +169 -0
  18. package/completions/_forgeloop +7 -1
  19. package/completions/forgeloop.bash +13 -1
  20. package/completions/forgeloop.fish +40 -1
  21. package/docs/AGENT_PROTOCOL_SUMMARY.md +8 -1
  22. package/docs/CLI_REFERENCE.md +114 -2
  23. package/docs/DOCUMENTATION_GUIDE.md +10 -5
  24. package/docs/GETTING_STARTED.md +17 -0
  25. package/docs/MCP.md +16 -1
  26. package/docs/PACKAGE_CONTENTS.md +7 -1
  27. package/docs/PERSISTENT_SEARCH_TRANSPORT.md +289 -0
  28. package/docs/RECIPES.md +30 -0
  29. package/docs/RELEASE_CHECKLIST.md +34 -0
  30. package/docs/REPOSITORY_INDEX.md +553 -0
  31. package/docs/TROUBLESHOOTING.md +147 -0
  32. package/docs/UNIVERSAL_INTEGRATION.md +30 -0
  33. package/docs/diagrams/README.md +10 -0
  34. package/package.json +11 -2
  35. package/scripts/update-tgrep-manifest.mjs +86 -0
  36. package/scripts/verify-tgrep-manifest.mjs +17 -0
  37. package/src/cli.js +35 -0
  38. package/src/commands/doctor.js +76 -1
  39. package/src/commands/index-rebuild.js +1 -0
  40. package/src/commands/index-setup.js +1 -0
  41. package/src/commands/index-start.js +1 -0
  42. package/src/commands/index-status.js +1 -0
  43. package/src/commands/index-stop.js +1 -0
  44. package/src/commands/init.js +39 -1
  45. package/src/commands/repository-index.js +111 -0
  46. package/src/commands/search.js +1 -0
  47. package/src/commands/update.js +32 -4
  48. package/src/core/cli-command-definitions.js +97 -3
  49. package/src/core/command-executors.js +35 -2
  50. package/src/core/command-input.js +23 -0
  51. package/src/core/error-codes.js +195 -0
  52. package/src/core/filesystem.js +10 -1
  53. package/src/core/integration-invocation-policy.js +27 -0
  54. package/src/core/integration-resources.js +16 -1
  55. package/src/core/protocol-info.js +23 -0
  56. package/src/integration.d.ts +90 -0
  57. package/src/integration.js +21 -0
  58. package/src/persistent-transport/client.js +293 -0
  59. package/src/persistent-transport/constants.js +24 -0
  60. package/src/persistent-transport/errors.js +38 -0
  61. package/src/persistent-transport/framing.js +61 -0
  62. package/src/persistent-transport/lifecycle.js +116 -0
  63. package/src/persistent-transport/ownership.js +184 -0
  64. package/src/persistent-transport/paths.js +31 -0
  65. package/src/persistent-transport/protocol.js +95 -0
  66. package/src/persistent-transport/server.js +256 -0
  67. package/src/persistent-transport/state.js +49 -0
  68. package/src/repository-index/args.js +59 -0
  69. package/src/repository-index/binary-manager.js +413 -0
  70. package/src/repository-index/constants.js +45 -0
  71. package/src/repository-index/errors.js +38 -0
  72. package/src/repository-index/lifecycle.js +17 -0
  73. package/src/repository-index/lock.js +113 -0
  74. package/src/repository-index/manifest.js +132 -0
  75. package/src/repository-index/metrics.js +30 -0
  76. package/src/repository-index/normalize-json.js +187 -0
  77. package/src/repository-index/paths.js +39 -0
  78. package/src/repository-index/platform.js +20 -0
  79. package/src/repository-index/process.js +140 -0
  80. package/src/repository-index/readiness.js +62 -0
  81. package/src/repository-index/search.js +262 -0
  82. package/src/repository-index/server.js +432 -0
  83. package/src/repository-index/status.js +397 -0
  84. package/src/repository-index/tgrep-manifest.json +38 -0
@@ -0,0 +1,397 @@
1
+ import { execFile as nodeExecFile } from "node:child_process";
2
+ import { lstat, readFile, realpath } from "node:fs/promises";
3
+ import path from "node:path";
4
+
5
+ import { REPOSITORY_INDEX_DEFAULTS, REPOSITORY_INDEX_HEALTH, REPOSITORY_INDEX_SCHEMA_VERSION } from "./constants.js";
6
+ import { REPOSITORY_INDEX_ERROR_CODES } from "./errors.js";
7
+ import { verifyManagedTgrep } from "./binary-manager.js";
8
+ import { getCanonicalRepositoryIndexArgs } from "./args.js";
9
+ import { createTgrepBinaryHandle, runTgrep } from "./process.js";
10
+ import { getRepositoryIndexStatePath, getTgrepIndexPath } from "./paths.js";
11
+ import { getPackageRoot } from "../core/templates.js";
12
+
13
+ async function readJsonIfPresent(filePath) {
14
+ try {
15
+ const info = await lstat(filePath);
16
+ if (info.isSymbolicLink() || !info.isFile()) return { error: "NOT_REGULAR_FILE" };
17
+ const raw = await readFile(filePath, "utf8");
18
+ return { value: JSON.parse(raw) };
19
+ } catch (error) {
20
+ if (error.code === "ENOENT") return { value: null };
21
+ return { error: error.message };
22
+ }
23
+ }
24
+
25
+ export function processIsAlive(pid, processApi = process) {
26
+ if (!Number.isInteger(pid) || pid <= 0) return false;
27
+ try {
28
+ processApi.kill(pid, 0);
29
+ return true;
30
+ } catch (error) {
31
+ return error.code === "EPERM";
32
+ }
33
+ }
34
+
35
+ function execFileAsync(execFileImpl, file, args) {
36
+ return new Promise((resolve) => {
37
+ execFileImpl(file, args, { shell: false, timeout: 5_000, maxBuffer: 128 * 1024 }, (error, stdout, stderr) => {
38
+ resolve({ error, stdout: stdout ?? "", stderr: stderr ?? "" });
39
+ });
40
+ });
41
+ }
42
+
43
+ export async function processCommandLine(pid, { platform = process.platform, execFileImpl = nodeExecFile } = {}) {
44
+ if (!Number.isInteger(pid) || pid <= 0) return null;
45
+ if (platform === "win32") {
46
+ // PowerShell is part of supported Windows hosts. Encode the command as
47
+ // UTF-16LE instead of relying on Windows argument re-quoting for the
48
+ // nested CIM filter. The interpolated value is already an integer, so
49
+ // this remains an exact non-shell process query rather than a
50
+ // user-controlled command string.
51
+ const command = [
52
+ "$ErrorActionPreference = 'Stop'",
53
+ "$OutputEncoding = [System.Text.Encoding]::UTF8",
54
+ "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8",
55
+ `(Get-CimInstance -ClassName Win32_Process -Filter 'ProcessId = ${pid}').CommandLine`,
56
+ ].join("; ");
57
+ const encodedCommand = Buffer.from(command, "utf16le").toString("base64");
58
+ const result = await execFileAsync(execFileImpl, "powershell.exe", [
59
+ "-NoProfile",
60
+ "-NonInteractive",
61
+ "-EncodedCommand",
62
+ encodedCommand,
63
+ ]);
64
+ if (result.error) return null;
65
+ const commandLine = result.stdout.replaceAll("\0", "").trim();
66
+ return commandLine || null;
67
+ }
68
+ const result = await execFileAsync(execFileImpl, "ps", ["-p", String(pid), "-o", "command="]);
69
+ if (result.error) return null;
70
+ const command = result.stdout.trim();
71
+ return command || null;
72
+ }
73
+
74
+ function parseStatusText(text) {
75
+ const files = Number(text.match(/^\s*Files:\s*(\d+)/mi)?.[1]);
76
+ const trigrams = Number(text.match(/^\s*Trigrams:\s*(\d+)/mi)?.[1]);
77
+ const serverNotRunning = /Server:\s*not running/i.test(text);
78
+ const serverRunning = /Server:\s*running/i.test(text);
79
+ const indexing = text.match(/^\s*(?:Indexing|Status):\s*(.+)$/mi)?.[1]?.trim() ?? null;
80
+ return {
81
+ files: Number.isFinite(files) ? files : null,
82
+ trigrams: Number.isFinite(trigrams) ? trigrams : null,
83
+ serverNotRunning,
84
+ serverRunning,
85
+ indexing,
86
+ };
87
+ }
88
+
89
+ function samePath(left, right, platform = process.platform) {
90
+ const pathApi = platform === "win32" ? path.win32 : path;
91
+ const normalize = (value) => pathApi.normalize(value);
92
+ const comparable = (value) => platform === "win32" ? value.toLowerCase() : value;
93
+ return typeof left === "string" && typeof right === "string"
94
+ && comparable(normalize(left)) === comparable(normalize(right));
95
+ }
96
+
97
+ function stateMatchesIndexPaths(state, repositoryRoot, indexPath, platform) {
98
+ return samePath(state?.repositoryRoot, repositoryRoot, platform)
99
+ && samePath(state?.indexPath, indexPath, platform);
100
+ }
101
+
102
+ function processIdentityReason({ commandLine, repositoryRoot, indexPath, binaryPath, platform }) {
103
+ const comparableCommandLine = platform === "win32" && typeof commandLine === "string"
104
+ ? commandLine.toLowerCase()
105
+ : commandLine;
106
+ const comparableRoot = platform === "win32" ? repositoryRoot.toLowerCase() : repositoryRoot;
107
+ const comparableIndexPath = platform === "win32" ? indexPath.toLowerCase() : indexPath;
108
+ if (typeof commandLine !== "string" || !comparableCommandLine.includes("serve")
109
+ || !comparableCommandLine.includes(comparableRoot) || !comparableCommandLine.includes(comparableIndexPath)) {
110
+ return "PROCESS_IDENTITY_UNVERIFIED";
111
+ }
112
+ const comparableBinaryPath = platform === "win32" && binaryPath
113
+ ? binaryPath.toLowerCase()
114
+ : binaryPath;
115
+ if (binaryPath && !comparableCommandLine.includes(comparableBinaryPath)) return "PROCESS_BINARY_MISMATCH";
116
+ return null;
117
+ }
118
+
119
+ async function readProcessCommandLineForInspection(pid, processInspector, platform) {
120
+ return processInspector.commandLine === undefined
121
+ ? processCommandLine(pid, { ...processInspector, platform })
122
+ : processInspector.commandLine(pid);
123
+ }
124
+
125
+ export async function inspectRepositoryIndexServer({ state, serve, repositoryRoot, indexPath, processInspector = {} } = {}) {
126
+ const platform = processInspector.platform ?? process.platform;
127
+ if (!state || state.startedByForgeLoop !== true) {
128
+ return { owned: false, running: false, reason: "MISSING_FORGELOOP_OWNERSHIP" };
129
+ }
130
+ if (!stateMatchesIndexPaths(state, repositoryRoot, indexPath, platform)) {
131
+ return { owned: false, running: false, reason: "STATE_PATH_MISMATCH" };
132
+ }
133
+ if (!serve || serve.pid !== state.observedPid || !Number.isInteger(serve.pid) || serve.pid <= 0) {
134
+ return { owned: false, running: false, reason: "SERVE_METADATA_MISMATCH" };
135
+ }
136
+ const alive = (processInspector.isAlive ?? processIsAlive)(serve.pid);
137
+ if (!alive) return { owned: true, running: false, pid: serve.pid, port: serve.port ?? null, reason: "PROCESS_EXITED" };
138
+ const commandLine = await readProcessCommandLineForInspection(serve.pid, processInspector, platform);
139
+ const identityReason = processIdentityReason({ commandLine, repositoryRoot, indexPath, binaryPath: state.binaryPath, platform });
140
+ if (identityReason) return { owned: false, running: false, pid: serve.pid, port: serve.port ?? null, reason: identityReason, commandLine: null };
141
+ return { owned: true, running: true, pid: serve.pid, port: serve.port ?? null, commandLine: null };
142
+ }
143
+
144
+ function baseStatus({ repositoryRoot, indexPath, statePath }) {
145
+ return {
146
+ schemaVersion: REPOSITORY_INDEX_SCHEMA_VERSION,
147
+ required: true,
148
+ engine: "tgrep",
149
+ engineVersion: null,
150
+ managedBinary: false,
151
+ overridden: false,
152
+ binaryPath: null,
153
+ repositoryRoot,
154
+ indexPath,
155
+ statePath,
156
+ policy: {
157
+ maxFileSize: REPOSITORY_INDEX_DEFAULTS.maxFileSize,
158
+ maxCpuPercent: REPOSITORY_INDEX_DEFAULTS.maxCpuPercent,
159
+ watcherQueueCap: REPOSITORY_INDEX_DEFAULTS.watcherQueueCap,
160
+ autoSaveMutations: REPOSITORY_INDEX_DEFAULTS.autoSaveMutations,
161
+ },
162
+ index: {
163
+ present: false,
164
+ complete: false,
165
+ files: null,
166
+ trigrams: null,
167
+ createdAt: null,
168
+ updatedAt: null,
169
+ rootPath: null,
170
+ },
171
+ server: {
172
+ running: false,
173
+ owned: false,
174
+ pid: null,
175
+ port: null,
176
+ watcher: "inactive",
177
+ indexing: "unknown",
178
+ files: null,
179
+ },
180
+ health: "NOT_INITIALIZED",
181
+ diagnostics: [],
182
+ };
183
+ }
184
+
185
+ function redactLocalPaths(message, paths = []) {
186
+ let redacted = typeof message === "string" ? message : String(message ?? "");
187
+ for (const value of paths.filter((item) => typeof item === "string" && item.length > 0).sort((left, right) => right.length - left.length)) {
188
+ redacted = redacted.replaceAll(value, "<local-path>");
189
+ }
190
+ return redacted;
191
+ }
192
+
193
+ export function sanitizeRepositoryIndexStatus(status) {
194
+ if (!status || typeof status !== "object") return status;
195
+ const {
196
+ binaryPath: _binaryPath,
197
+ repositoryRoot: _repositoryRoot,
198
+ indexPath: _indexPath,
199
+ statePath: _statePath,
200
+ index = {},
201
+ diagnostics = [],
202
+ ...publicStatus
203
+ } = status;
204
+ const localPaths = [
205
+ status.binaryPath,
206
+ status.repositoryRoot,
207
+ status.indexPath,
208
+ status.statePath,
209
+ index.rootPath,
210
+ ...diagnostics.map((diagnostic) => diagnostic.path),
211
+ ];
212
+ const { rootPath: _rootPath, ...publicIndex } = index;
213
+ return {
214
+ ...publicStatus,
215
+ index: publicIndex,
216
+ diagnostics: diagnostics.map(({ path: _path, ...diagnostic }) => ({
217
+ ...diagnostic,
218
+ message: redactLocalPaths(diagnostic.message, localPaths),
219
+ })),
220
+ };
221
+ }
222
+
223
+ function statusOutput(status, includeLocalPaths) {
224
+ return includeLocalPaths ? status : sanitizeRepositoryIndexStatus(status);
225
+ }
226
+
227
+ function healthResult(status, health, diagnostic) {
228
+ status.health = REPOSITORY_INDEX_HEALTH.includes(health) ? health : "ERROR";
229
+ if (diagnostic) status.diagnostics.push(diagnostic);
230
+ return status;
231
+ }
232
+
233
+ async function verifyEngineForStatus(status, options) {
234
+ try {
235
+ const engine = await verifyManagedTgrep(options);
236
+ status.engineVersion = engine.engineVersion;
237
+ status.managedBinary = engine.managed;
238
+ status.overridden = engine.overridden;
239
+ status.binaryPath = engine.binaryPath;
240
+ return engine;
241
+ } catch (error) {
242
+ const missing = error.code === REPOSITORY_INDEX_ERROR_CODES.ENGINE_MISSING;
243
+ healthResult(status, missing ? "ENGINE_MISSING" : "ENGINE_INVALID", {
244
+ code: error.code ?? REPOSITORY_INDEX_ERROR_CODES.ENGINE_MISSING,
245
+ message: error.message,
246
+ path: error.path,
247
+ });
248
+ return null;
249
+ }
250
+ }
251
+
252
+ async function readStatusMetadata(indexPath, statePath) {
253
+ const [metaFile, stateFile, serveFile] = await Promise.all([
254
+ readJsonIfPresent(path.join(indexPath, "meta.json")),
255
+ readJsonIfPresent(statePath),
256
+ readJsonIfPresent(path.join(indexPath, "serve.json")),
257
+ ]);
258
+ if (metaFile.error || stateFile.error || serveFile.error) {
259
+ return { error: { code: REPOSITORY_INDEX_ERROR_CODES.SERVER_UNHEALTHY, message: "Repository Index metadata is not readable as regular JSON files" } };
260
+ }
261
+ return { meta: metaFile.value, state: stateFile.value, serve: serveFile.value };
262
+ }
263
+
264
+ async function projectIndexMetadata(status, meta, canonicalRoot, config) {
265
+ const metaRoot = typeof meta.root_path === "string" ? await realpath(meta.root_path).catch(() => meta.root_path) : null;
266
+ status.index = {
267
+ present: true,
268
+ complete: meta.complete === true,
269
+ files: Number.isInteger(meta.num_files) ? meta.num_files : null,
270
+ trigrams: Number.isInteger(meta.num_trigrams) ? meta.num_trigrams : null,
271
+ createdAt: Number.isFinite(meta.created_at) ? meta.created_at : null,
272
+ updatedAt: Number.isFinite(meta.updated_at) ? meta.updated_at : null,
273
+ rootPath: metaRoot,
274
+ };
275
+ status.policy = {
276
+ maxFileSize: config.maxFileSize ?? REPOSITORY_INDEX_DEFAULTS.maxFileSize,
277
+ maxCpuPercent: config.maxCpuPercent ?? REPOSITORY_INDEX_DEFAULTS.maxCpuPercent,
278
+ watcherQueueCap: config.watcherQueueCap ?? REPOSITORY_INDEX_DEFAULTS.watcherQueueCap,
279
+ autoSaveMutations: config.autoSaveMutations ?? REPOSITORY_INDEX_DEFAULTS.autoSaveMutations,
280
+ };
281
+ if (!samePath(metaRoot, canonicalRoot)) {
282
+ healthResult(status, "SERVER_UNHEALTHY", { code: REPOSITORY_INDEX_ERROR_CODES.SERVER_UNHEALTHY, message: "Index metadata points at a different repository root" });
283
+ return false;
284
+ }
285
+ if (meta.complete !== true) {
286
+ status.server.indexing = "in-progress";
287
+ healthResult(status, "INDEXING", { code: REPOSITORY_INDEX_ERROR_CODES.INDEXING, message: "Repository Index build is incomplete" });
288
+ return false;
289
+ }
290
+ return true;
291
+ }
292
+
293
+ async function readNativeStatus({ engine, canonicalRoot, indexPath, config, spawnImpl }) {
294
+ return runTgrep({
295
+ binary: createTgrepBinaryHandle(engine.binaryPath),
296
+ repoRoot: canonicalRoot,
297
+ args: ["status", ...getCanonicalRepositoryIndexArgs({ mode: "status", indexPath, config }), canonicalRoot],
298
+ spawnImpl,
299
+ timeoutMs: 15_000,
300
+ maxOutputBytes: 256 * 1024,
301
+ }).catch((error) => ({ error }));
302
+ }
303
+
304
+ function nativeStatusFailed(native) {
305
+ return native.error || (native.exitCode !== 0 && !native.stdout && !native.stderr);
306
+ }
307
+
308
+ function projectServerStatus(status, ownership, serve, state) {
309
+ status.server = {
310
+ running: ownership.running === true,
311
+ owned: ownership.owned === true,
312
+ pid: ownership.pid ?? null,
313
+ port: ownership.port ?? null,
314
+ watcher: ownership.running ? "active" : "inactive",
315
+ indexing: "complete",
316
+ files: status.index.files,
317
+ };
318
+ if (ownership.running) return healthResult(status, "READY");
319
+ if (serve || state) {
320
+ return healthResult(status, ownership.owned ? "SERVER_DOWN" : "SERVER_UNHEALTHY", {
321
+ code: REPOSITORY_INDEX_ERROR_CODES.SERVER_UNHEALTHY,
322
+ message: ownership.owned ? "ForgeLoop Repository Index server is not running" : "A tgrep server exists without verifiable ForgeLoop ownership",
323
+ });
324
+ }
325
+ return healthResult(status, "SERVER_DOWN", { code: REPOSITORY_INDEX_ERROR_CODES.SERVER_UNHEALTHY, message: "Repository Index server is not running" });
326
+ }
327
+
328
+ export async function getRepositoryIndexStatus(repositoryRoot, {
329
+ packageRoot = getPackageRoot(),
330
+ env = process.env,
331
+ binaryPath,
332
+ platform,
333
+ arch,
334
+ homeDirectory,
335
+ config = {},
336
+ spawnImpl,
337
+ processInspector = {},
338
+ skipNativeStatus = false,
339
+ includeLocalPaths = false,
340
+ } = {}) {
341
+ const canonicalRoot = await realpath(repositoryRoot);
342
+ const indexPath = getTgrepIndexPath(canonicalRoot);
343
+ const statePath = getRepositoryIndexStatePath(canonicalRoot);
344
+ const status = baseStatus({ repositoryRoot: canonicalRoot, indexPath, statePath });
345
+
346
+ const engine = await verifyEngineForStatus(status, {
347
+ packageRoot,
348
+ repoRoot: canonicalRoot,
349
+ env,
350
+ binaryPath,
351
+ platform,
352
+ arch,
353
+ homeDirectory,
354
+ spawnImpl,
355
+ });
356
+ if (!engine) return statusOutput(status, includeLocalPaths);
357
+
358
+ const metadata = await readStatusMetadata(indexPath, statePath);
359
+ if (metadata.error) return statusOutput(healthResult(status, "SERVER_UNHEALTHY", metadata.error), includeLocalPaths);
360
+ const { meta, state, serve } = metadata;
361
+ if (!meta) return statusOutput(healthResult(status, "NOT_INITIALIZED", { code: REPOSITORY_INDEX_ERROR_CODES.NOT_INITIALIZED, message: "Repository Index has not been built" }), includeLocalPaths);
362
+ if (!await projectIndexMetadata(status, meta, canonicalRoot, config)) return statusOutput(status, includeLocalPaths);
363
+
364
+ if (!skipNativeStatus) {
365
+ const native = await readNativeStatus({ engine, canonicalRoot, indexPath, config, spawnImpl });
366
+ if (nativeStatusFailed(native)) {
367
+ return statusOutput(healthResult(status, "SERVER_UNHEALTHY", {
368
+ code: native.error?.code ?? REPOSITORY_INDEX_ERROR_CODES.SERVER_UNHEALTHY,
369
+ message: native.error?.message ?? "tgrep status failed",
370
+ }), includeLocalPaths);
371
+ }
372
+ status.native = parseStatusText(`${native.stdout}\n${native.stderr}`);
373
+ }
374
+
375
+ const ownership = await inspectRepositoryIndexServer({
376
+ state,
377
+ serve,
378
+ repositoryRoot: canonicalRoot,
379
+ indexPath,
380
+ processInspector,
381
+ });
382
+ return statusOutput(projectServerStatus(status, ownership, serve, state), includeLocalPaths);
383
+ }
384
+
385
+ export function formatRepositoryIndexStatus(status) {
386
+ const lines = [
387
+ `Repository Index: ${status.health}`,
388
+ `Engine: ${status.engine} ${status.engineVersion ?? "unknown"}${status.overridden ? " (explicit override)" : ""}`,
389
+ `Repository: ${status.repositoryRoot ?? "hidden"}`,
390
+ `Index: ${status.indexPath ?? "hidden"}`,
391
+ `Indexed files: ${status.index.files ?? "unknown"}`,
392
+ `Server: ${status.server.running ? `running (pid ${status.server.pid ?? "unknown"}, port ${status.server.port ?? "unknown"})` : "not running"}`,
393
+ `Watcher: ${status.server.watcher}`,
394
+ ];
395
+ for (const diagnostic of status.diagnostics ?? []) lines.push(`Diagnostic: ${diagnostic.code}: ${diagnostic.message}`);
396
+ return `${lines.join("\n")}\n`;
397
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "engine": "tgrep",
4
+ "repository": "microsoft/tgrep",
5
+ "version": "1.0.3",
6
+ "license": "MIT",
7
+ "releaseBaseUrl": "https://github.com/microsoft/tgrep/releases/download/v1.0.3",
8
+ "assets": {
9
+ "darwin-arm64": {
10
+ "assetName": "tgrep-v1.0.3-aarch64-apple-darwin.tar.gz",
11
+ "archive": "tar.gz",
12
+ "binaryName": "tgrep",
13
+ "sha256": "d91f1662148d5dcea85710e34f8e2830ca33c041f6b5232ecf7650b7e9ea222b",
14
+ "binarySha256": "0a2a15bf794bad8273e2043474a9555032ba7ab9ed8da325a4a5af4a0961d7dd"
15
+ },
16
+ "darwin-x64": {
17
+ "assetName": "tgrep-v1.0.3-x86_64-apple-darwin.tar.gz",
18
+ "archive": "tar.gz",
19
+ "binaryName": "tgrep",
20
+ "sha256": "58b9bfb9b2d4fa7c4cb3ef49202ef98b6d3812c60fd0ed2f00ed7a5a5d9046d7",
21
+ "binarySha256": "9a8778094f73eaf097802b8d7c593e0005f10958470cdbb1b16fde0a78da4981"
22
+ },
23
+ "linux-x64": {
24
+ "assetName": "tgrep-v1.0.3-x86_64-unknown-linux-musl.tar.gz",
25
+ "archive": "tar.gz",
26
+ "binaryName": "tgrep",
27
+ "sha256": "ef97d99225d1454a7c18e4b632cee25c125b7321d8ec50adf464903f0ddf38ae",
28
+ "binarySha256": "d788c5323f1cd6e610e3f8629fc7797ec0ddaaf602027114c7ad508e12bd5d48"
29
+ },
30
+ "windows-x64": {
31
+ "assetName": "tgrep-v1.0.3-x86_64-pc-windows-msvc.zip",
32
+ "archive": "zip",
33
+ "binaryName": "tgrep.exe",
34
+ "sha256": "922b6c8657c0f910cbc2957adc06286cb9ce1953d46ea1d0ef44dc7b7649b384",
35
+ "binarySha256": "77070e3ebf4125d71678606cb71b409175419a377eb65dcc09f1617d99ecf3a6"
36
+ }
37
+ }
38
+ }