@crafter/cli-tree 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 (104) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +328 -0
  3. package/dist/archaeology/cache.d.ts +11 -0
  4. package/dist/archaeology/delegate.d.ts +43 -0
  5. package/dist/archaeology/index.d.ts +12 -0
  6. package/dist/archaeology/index.js +61 -0
  7. package/dist/archaeology/index.js.map +9 -0
  8. package/dist/archaeology/llm.d.ts +1 -0
  9. package/dist/archaeology/merge.d.ts +3 -0
  10. package/dist/archaeology/orchestrator.d.ts +25 -0
  11. package/dist/archaeology/prompts.d.ts +13 -0
  12. package/dist/archaeology/types.d.ts +101 -0
  13. package/dist/archaeology/validate.d.ts +18 -0
  14. package/dist/chunk-57gtsvhb.js +434 -0
  15. package/dist/chunk-57gtsvhb.js.map +16 -0
  16. package/dist/chunk-5aahbfr2.js +293 -0
  17. package/dist/chunk-5aahbfr2.js.map +10 -0
  18. package/dist/chunk-pkfpaae1.js +678 -0
  19. package/dist/chunk-pkfpaae1.js.map +15 -0
  20. package/dist/chunk-q4se2rwe.js +181 -0
  21. package/dist/chunk-q4se2rwe.js.map +14 -0
  22. package/dist/chunk-v5w3w6bd.js +168 -0
  23. package/dist/chunk-v5w3w6bd.js.map +11 -0
  24. package/dist/chunk-ykze151b.js +770 -0
  25. package/dist/chunk-ykze151b.js.map +16 -0
  26. package/dist/cli.d.ts +2 -0
  27. package/dist/cli.js +433 -0
  28. package/dist/cli.js.map +10 -0
  29. package/dist/encoders/ansi.d.ts +2 -0
  30. package/dist/encoders/html.d.ts +10 -0
  31. package/dist/encoders/string.d.ts +2 -0
  32. package/dist/flow/encode.d.ts +5 -0
  33. package/dist/flow/index.d.ts +8 -0
  34. package/dist/flow/index.js +25 -0
  35. package/dist/flow/index.js.map +9 -0
  36. package/dist/flow/layout.d.ts +30 -0
  37. package/dist/flow/parse.d.ts +2 -0
  38. package/dist/flow/render.d.ts +3 -0
  39. package/dist/flow/types.d.ts +42 -0
  40. package/dist/flow/validate.d.ts +3 -0
  41. package/dist/flow/yaml.d.ts +4 -0
  42. package/dist/grid.d.ts +14 -0
  43. package/dist/index.d.ts +7 -0
  44. package/dist/index.js +21 -0
  45. package/dist/index.js.map +9 -0
  46. package/dist/miner/history.d.ts +6 -0
  47. package/dist/miner/index.d.ts +18 -0
  48. package/dist/miner/index.js +38 -0
  49. package/dist/miner/index.js.map +9 -0
  50. package/dist/miner/sessions.d.ts +3 -0
  51. package/dist/miner/stats.d.ts +2 -0
  52. package/dist/miner/suggest.d.ts +11 -0
  53. package/dist/miner/transitions.d.ts +6 -0
  54. package/dist/miner/types.d.ts +46 -0
  55. package/dist/miner/workflows.d.ts +11 -0
  56. package/dist/parse.d.ts +3 -0
  57. package/dist/render.d.ts +3 -0
  58. package/dist/types.d.ts +39 -0
  59. package/package.json +85 -0
  60. package/skill/SKILL.md +263 -0
  61. package/skill/evals/evals.json +26 -0
  62. package/skill/install.sh +38 -0
  63. package/skill/references/archaeology-guide.md +157 -0
  64. package/skill/references/skill-template.md +120 -0
  65. package/src/archaeology/cache.ts +107 -0
  66. package/src/archaeology/delegate.ts +113 -0
  67. package/src/archaeology/index.ts +48 -0
  68. package/src/archaeology/llm.ts +10 -0
  69. package/src/archaeology/merge.ts +155 -0
  70. package/src/archaeology/orchestrator.ts +185 -0
  71. package/src/archaeology/prompts.ts +178 -0
  72. package/src/archaeology/types.ts +139 -0
  73. package/src/archaeology/validate.ts +157 -0
  74. package/src/cli.ts +451 -0
  75. package/src/encoders/ansi.ts +32 -0
  76. package/src/encoders/html.ts +78 -0
  77. package/src/encoders/string.ts +20 -0
  78. package/src/flow/encode.ts +21 -0
  79. package/src/flow/index.ts +15 -0
  80. package/src/flow/layout.ts +150 -0
  81. package/src/flow/parse.ts +100 -0
  82. package/src/flow/render.ts +186 -0
  83. package/src/flow/types.ts +45 -0
  84. package/src/flow/validate.ts +111 -0
  85. package/src/flow/yaml.ts +235 -0
  86. package/src/grid.ts +59 -0
  87. package/src/index.ts +24 -0
  88. package/src/miner/history.ts +156 -0
  89. package/src/miner/index.ts +76 -0
  90. package/src/miner/sessions.ts +39 -0
  91. package/src/miner/stats.ts +43 -0
  92. package/src/miner/suggest.ts +101 -0
  93. package/src/miner/transitions.ts +62 -0
  94. package/src/miner/types.ts +45 -0
  95. package/src/miner/workflows.ts +96 -0
  96. package/src/parse.ts +321 -0
  97. package/src/render.ts +182 -0
  98. package/src/types.ts +62 -0
  99. package/workflows/docker-deploy.yml +42 -0
  100. package/workflows/docker-parallel.yml +36 -0
  101. package/workflows/gh-issue-to-pr.yml +48 -0
  102. package/workflows/git-pr-flow.yml +36 -0
  103. package/workflows/kubectl-rollout.yml +37 -0
  104. package/workflows/npm-publish.yml +42 -0
@@ -0,0 +1,770 @@
1
+ import {
2
+ parseHelp,
3
+ parseHelpRecursive
4
+ } from "./chunk-5aahbfr2.js";
5
+
6
+ // src/archaeology/types.ts
7
+ function toCLINode(node) {
8
+ const base = { name: node.name };
9
+ if (node.description)
10
+ base.description = node.description;
11
+ if (node.aliases)
12
+ base.aliases = node.aliases;
13
+ if (node.subcommands)
14
+ base.subcommands = node.subcommands.map(toCLINode);
15
+ if (node.flags)
16
+ base.flags = node.flags.map((f) => ({ ...f }));
17
+ if (node.args)
18
+ base.args = node.args.map((a) => ({ ...a }));
19
+ return base;
20
+ }
21
+ function fromCLINode(node, source = { type: "help", confidence: 1 }) {
22
+ const base = { name: node.name, source };
23
+ if (node.description)
24
+ base.description = node.description;
25
+ if (node.aliases)
26
+ base.aliases = node.aliases;
27
+ if (node.subcommands)
28
+ base.subcommands = node.subcommands.map((s) => fromCLINode(s, source));
29
+ if (node.flags)
30
+ base.flags = node.flags.map((f) => ({ ...f, source }));
31
+ if (node.args)
32
+ base.args = node.args.map((a) => ({ ...a, source }));
33
+ return base;
34
+ }
35
+ // src/archaeology/prompts.ts
36
+ function surveyPrompt(ctx) {
37
+ return `You are a CLI archaeologist. Your task is to produce a hypothesis map of the \`${ctx.cli}\` CLI based on your training knowledge.
38
+
39
+ ${ctx.version ? `Version: ${ctx.version}
40
+ ` : ""}Do not invoke the CLI yet. This is survey phase — hypothesize what families of commands exist and what subcommands belong to each family.
41
+
42
+ Output STRICT JSON matching this schema:
43
+
44
+ \`\`\`json
45
+ {
46
+ "cli": "${ctx.cli}",
47
+ "description": "<one-line description of the CLI>",
48
+ "families": [
49
+ {
50
+ "name": "<family name, e.g. 'container management'>",
51
+ "description": "<one line>",
52
+ "subcommands": ["<subcommand-name>", ...]
53
+ }
54
+ ],
55
+ "topLevelFlags": ["--global-flag-1", "--global-flag-2"]
56
+ }
57
+ \`\`\`
58
+
59
+ Rules:
60
+ - Group related subcommands into families (container, image, network, auth, config, etc).
61
+ - List ONLY subcommand names — no descriptions in this phase.
62
+ - Include hidden/experimental commands you know about.
63
+ - topLevelFlags = flags that work on the root (e.g. --version, --help, --config).
64
+ - Output ONLY the JSON, no prose before or after.`;
65
+ }
66
+ function excavationPrompt(ctx, subcommandPath) {
67
+ const cmd = [ctx.cli, ...subcommandPath].join(" ");
68
+ return `You are a CLI archaeologist, currently excavating the \`${cmd}\` subcommand.
69
+
70
+ You will validate your hypothesis by running the real CLI. Use the Bash tool to run:
71
+
72
+ \`${cmd} --help\`
73
+
74
+ Then parse its output and produce a structured description of this subcommand.
75
+
76
+ Output STRICT JSON matching this schema:
77
+
78
+ \`\`\`json
79
+ {
80
+ "command": ${JSON.stringify([ctx.cli, ...subcommandPath])},
81
+ "exists": true|false,
82
+ "description": "<description from help output>",
83
+ "subcommands": [
84
+ { "name": "<sub>", "description": "<desc>" }
85
+ ],
86
+ "flags": [
87
+ {
88
+ "name": "flag-name",
89
+ "short": "f",
90
+ "type": "boolean|string|number|enum",
91
+ "description": "<desc>",
92
+ "required": false
93
+ }
94
+ ],
95
+ "args": [
96
+ { "name": "input", "required": true, "variadic": false }
97
+ ]
98
+ }
99
+ \`\`\`
100
+
101
+ Rules:
102
+ - If the command does not exist or --help fails, set exists: false and return empty arrays.
103
+ - Extract every flag and arg from the help output.
104
+ - Do NOT invent anything — only what is in the actual help text.
105
+ - Output ONLY the JSON.`;
106
+ }
107
+ function deepDivePrompt(ctx, subcommandPath) {
108
+ const cmd = [ctx.cli, ...subcommandPath].join(" ");
109
+ return `You are a CLI archaeologist. You already excavated \`${cmd}\` from --help.
110
+
111
+ Now dig deeper: based on your training knowledge, what flags exist for this command that are NOT documented in --help? Focus on:
112
+ - Hidden flags (work but undocumented)
113
+ - Experimental flags (marked as experimental in source code)
114
+ - Deprecated flags (still functional but hidden)
115
+ - Flags that only appear in man pages or shell completion scripts
116
+
117
+ Output STRICT JSON:
118
+
119
+ \`\`\`json
120
+ {
121
+ "command": ${JSON.stringify([ctx.cli, ...subcommandPath])},
122
+ "hiddenFlags": [
123
+ {
124
+ "name": "flag-name",
125
+ "short": "f",
126
+ "type": "boolean|string|number|enum",
127
+ "hidden": true,
128
+ "description": "<what it does>",
129
+ "source": "source-code|man-page|completion|community-known"
130
+ }
131
+ ],
132
+ "deprecatedFlags": [
133
+ {
134
+ "name": "old-flag",
135
+ "description": "Deprecated in favor of --new-flag",
136
+ "replacement": "new-flag"
137
+ }
138
+ ],
139
+ "examples": [
140
+ "${cmd} --example-use-case"
141
+ ]
142
+ }
143
+ \`\`\`
144
+
145
+ Rules:
146
+ - Only suggest flags you are confident about. Confidence > 70%.
147
+ - Prefer flags you have seen in real usage, PRs, issues, or man pages.
148
+ - If unsure, omit. This phase is about quality, not quantity.
149
+ - Output ONLY the JSON.`;
150
+ }
151
+ function cartographyPrompt(ctx, subcommandPath) {
152
+ const cmd = [ctx.cli, ...subcommandPath].join(" ");
153
+ return `You are a CLI archaeologist mapping flag interactions for \`${cmd}\`.
154
+
155
+ Identify constraints between flags:
156
+ - Mutually exclusive (cannot use together)
157
+ - Requires (using A requires B)
158
+ - Implies (using A sets B automatically)
159
+
160
+ Output STRICT JSON:
161
+
162
+ \`\`\`json
163
+ {
164
+ "command": ${JSON.stringify([ctx.cli, ...subcommandPath])},
165
+ "constraints": [
166
+ {
167
+ "type": "mutually-exclusive|requires|implies",
168
+ "flags": ["--flag-a", "--flag-b"],
169
+ "description": "<human explanation>"
170
+ }
171
+ ]
172
+ }
173
+ \`\`\`
174
+
175
+ Rules:
176
+ - Only list constraints you are confident about.
177
+ - Reference real flags from the --help output, not hypothetical ones.
178
+ - Output ONLY the JSON.`;
179
+ }
180
+ function validationInstructions(cli) {
181
+ return `You are validating an archaeology result for \`${cli}\`.
182
+
183
+ For each command/flag proposed by the LLM survey phase, you must:
184
+
185
+ 1. Run \`<cli> <subcommand> --help\` via the Bash tool.
186
+ 2. Parse the real help output.
187
+ 3. Mark each flag as:
188
+ - "verified" — appears in --help, can confirm it exists
189
+ - "rejected" — does NOT appear in --help, LLM hallucinated
190
+ - "unconfirmed" — not in --help but LLM may still be correct (hidden flags)
191
+
192
+ Return a validation report. Flags marked "rejected" must be removed from the final tree.
193
+ Flags marked "unconfirmed" are kept only if --include-unverified is set.`;
194
+ }
195
+ function extractJsonFromLlmResponse(response) {
196
+ const fenced = response.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
197
+ const raw = fenced ? fenced[1] : response;
198
+ const trimmed = raw.trim();
199
+ return JSON.parse(trimmed);
200
+ }
201
+ // src/archaeology/validate.ts
202
+ async function runHelpCommand(binary, subcommandPath) {
203
+ try {
204
+ const proc = Bun.spawn([binary, ...subcommandPath, "--help"], {
205
+ stdout: "pipe",
206
+ stderr: "pipe"
207
+ });
208
+ const timeout = setTimeout(() => proc.kill(), 5000);
209
+ const [stdout, stderr] = await Promise.all([
210
+ new Response(proc.stdout).text(),
211
+ new Response(proc.stderr).text()
212
+ ]);
213
+ clearTimeout(timeout);
214
+ await proc.exited;
215
+ const output = stdout || stderr;
216
+ return output.trim() ? output : null;
217
+ } catch {
218
+ return null;
219
+ }
220
+ }
221
+ async function validateSubcommandExists(binary, subcommandPath) {
222
+ const output = await runHelpCommand(binary, subcommandPath);
223
+ if (!output)
224
+ return false;
225
+ const lower = output.toLowerCase();
226
+ const hasError = /unknown (command|subcommand)|command not found|no such command/.test(lower) || /is not a \w+ command/.test(lower) || /invalid (command|subcommand)/.test(lower) || /error: unrecognized/.test(lower);
227
+ return !hasError;
228
+ }
229
+ async function validateSurvey(binary, survey) {
230
+ const confirmed = [];
231
+ const rejected = [];
232
+ const allSubs = survey.families.flatMap((f) => f.subcommands);
233
+ const unique = Array.from(new Set(allSubs));
234
+ const BATCH = 5;
235
+ for (let i = 0;i < unique.length; i += BATCH) {
236
+ const batch = unique.slice(i, i + BATCH);
237
+ const results = await Promise.all(batch.map(async (sub) => ({ sub, exists: await validateSubcommandExists(binary, [sub]) })));
238
+ for (const { sub, exists } of results) {
239
+ if (exists)
240
+ confirmed.push(sub);
241
+ else
242
+ rejected.push(sub);
243
+ }
244
+ }
245
+ return { confirmed, rejected };
246
+ }
247
+ async function excavateCommand(binary, subcommandPath) {
248
+ const output = await runHelpCommand(binary, subcommandPath);
249
+ if (!output)
250
+ return null;
251
+ const name = subcommandPath[subcommandPath.length - 1] ?? binary;
252
+ return parseHelp(name, output);
253
+ }
254
+ async function validateDeepDive(binary, proposal) {
255
+ const subcommandPath = proposal.command.slice(1);
256
+ const realHelp = await runHelpCommand(binary, subcommandPath);
257
+ if (!realHelp) {
258
+ return {
259
+ command: proposal.command,
260
+ exists: false,
261
+ verifiedFlags: [],
262
+ rejectedFlags: proposal.flags.map((f) => f.name),
263
+ unconfirmedFlags: [],
264
+ error: "Command did not produce help output"
265
+ };
266
+ }
267
+ const parsed = parseHelp(subcommandPath[subcommandPath.length - 1] ?? binary, realHelp);
268
+ const realFlagNames = new Set(parsed.flags?.map((f) => f.name) ?? []);
269
+ const verifiedFlags = [];
270
+ const rejectedFlags = [];
271
+ const unconfirmedFlags = [];
272
+ for (const flag of proposal.flags) {
273
+ if (realFlagNames.has(flag.name)) {
274
+ verifiedFlags.push(flag.name);
275
+ } else if (flag.hidden || flag.deprecated) {
276
+ unconfirmedFlags.push(flag.name);
277
+ } else {
278
+ rejectedFlags.push(flag.name);
279
+ }
280
+ }
281
+ return {
282
+ command: proposal.command,
283
+ exists: true,
284
+ verifiedFlags,
285
+ rejectedFlags,
286
+ unconfirmedFlags,
287
+ rawHelp: realHelp
288
+ };
289
+ }
290
+ function summarizeValidation(verdicts) {
291
+ let verified = 0;
292
+ let rejected = 0;
293
+ let unconfirmed = 0;
294
+ const hallucinations = [];
295
+ for (const v of verdicts) {
296
+ verified += v.verifiedFlags.length;
297
+ rejected += v.rejectedFlags.length;
298
+ unconfirmed += v.unconfirmedFlags.length;
299
+ for (const flag of v.rejectedFlags) {
300
+ hallucinations.push(`${v.command.join(" ")} --${flag}`);
301
+ }
302
+ }
303
+ return {
304
+ totalProposed: verified + rejected + unconfirmed,
305
+ verified,
306
+ rejected,
307
+ unconfirmed,
308
+ hallucinations
309
+ };
310
+ }
311
+ // src/archaeology/merge.ts
312
+ var SOURCE_PRIORITY = {
313
+ user: 100,
314
+ help: 90,
315
+ "llm-validated": 70,
316
+ man: 60,
317
+ completion: 55,
318
+ "llm-cartography": 40,
319
+ "llm-deep-dive": 35,
320
+ "llm-survey": 20
321
+ };
322
+ function sourceRank(source) {
323
+ if (!source)
324
+ return 0;
325
+ return SOURCE_PRIORITY[source.type] ?? 0;
326
+ }
327
+ function mergeNodes(a, b) {
328
+ if (a.name !== b.name) {
329
+ throw new Error(`Cannot merge nodes with different names: ${a.name} vs ${b.name}`);
330
+ }
331
+ const highPriority = sourceRank(a.source) >= sourceRank(b.source) ? a : b;
332
+ const lowPriority = highPriority === a ? b : a;
333
+ const merged = {
334
+ name: a.name,
335
+ source: highPriority.source
336
+ };
337
+ merged.description = highPriority.description ?? lowPriority.description;
338
+ if (highPriority.aliases || lowPriority.aliases) {
339
+ const set = new Set([...highPriority.aliases ?? [], ...lowPriority.aliases ?? []]);
340
+ merged.aliases = Array.from(set);
341
+ }
342
+ merged.hidden = highPriority.hidden ?? lowPriority.hidden;
343
+ merged.deprecated = highPriority.deprecated ?? lowPriority.deprecated;
344
+ merged.experimental = highPriority.experimental ?? lowPriority.experimental;
345
+ merged.flags = mergeFlags(highPriority.flags ?? [], lowPriority.flags ?? []);
346
+ if (merged.flags.length === 0)
347
+ delete merged.flags;
348
+ merged.args = mergeArgs(highPriority.args ?? [], lowPriority.args ?? []);
349
+ if (merged.args.length === 0)
350
+ delete merged.args;
351
+ merged.subcommands = mergeSubcommands(highPriority.subcommands ?? [], lowPriority.subcommands ?? []);
352
+ if (merged.subcommands.length === 0)
353
+ delete merged.subcommands;
354
+ if (highPriority.constraints || lowPriority.constraints) {
355
+ merged.constraints = [...highPriority.constraints ?? [], ...lowPriority.constraints ?? []];
356
+ }
357
+ if (highPriority.examples || lowPriority.examples) {
358
+ merged.examples = dedupeExamples([...highPriority.examples ?? [], ...lowPriority.examples ?? []]);
359
+ }
360
+ return merged;
361
+ }
362
+ function mergeFlags(primary, secondary) {
363
+ const byName = new Map;
364
+ for (const flag of primary) {
365
+ byName.set(flag.name, flag);
366
+ }
367
+ for (const flag of secondary) {
368
+ const existing = byName.get(flag.name);
369
+ if (!existing) {
370
+ byName.set(flag.name, flag);
371
+ } else {
372
+ byName.set(flag.name, mergeFlag(existing, flag));
373
+ }
374
+ }
375
+ return Array.from(byName.values()).sort((a, b) => a.name.localeCompare(b.name));
376
+ }
377
+ function mergeFlag(a, b) {
378
+ const high = sourceRank(a.source) >= sourceRank(b.source) ? a : b;
379
+ const low = high === a ? b : a;
380
+ return {
381
+ ...low,
382
+ ...high,
383
+ description: high.description ?? low.description,
384
+ short: high.short ?? low.short,
385
+ examples: [...high.examples ?? [], ...low.examples ?? []],
386
+ excludes: Array.from(new Set([...high.excludes ?? [], ...low.excludes ?? []])),
387
+ requires: Array.from(new Set([...high.requires ?? [], ...low.requires ?? []]))
388
+ };
389
+ }
390
+ function mergeArgs(primary, secondary) {
391
+ const byName = new Map;
392
+ for (const arg of primary)
393
+ byName.set(arg.name, arg);
394
+ for (const arg of secondary) {
395
+ if (!byName.has(arg.name))
396
+ byName.set(arg.name, arg);
397
+ }
398
+ return Array.from(byName.values());
399
+ }
400
+ function mergeSubcommands(primary, secondary) {
401
+ const byName = new Map;
402
+ for (const sub of primary)
403
+ byName.set(sub.name, sub);
404
+ for (const sub of secondary) {
405
+ const existing = byName.get(sub.name);
406
+ if (!existing) {
407
+ byName.set(sub.name, sub);
408
+ } else {
409
+ byName.set(sub.name, mergeNodes(existing, sub));
410
+ }
411
+ }
412
+ return Array.from(byName.values()).sort((a, b) => a.name.localeCompare(b.name));
413
+ }
414
+ function dedupeExamples(examples) {
415
+ const byCommand = new Map;
416
+ for (const ex of examples) {
417
+ const existing = byCommand.get(ex.command);
418
+ if (!existing || sourceRank(ex.source) > sourceRank(existing.source)) {
419
+ byCommand.set(ex.command, ex);
420
+ }
421
+ }
422
+ return Array.from(byCommand.values());
423
+ }
424
+ function filterUnverified(node) {
425
+ const copy = { ...node };
426
+ if (copy.flags) {
427
+ copy.flags = copy.flags.filter((f) => {
428
+ const rank = sourceRank(f.source);
429
+ return rank >= SOURCE_PRIORITY["llm-validated"];
430
+ });
431
+ if (copy.flags.length === 0)
432
+ delete copy.flags;
433
+ }
434
+ if (copy.subcommands) {
435
+ copy.subcommands = copy.subcommands.filter((s) => {
436
+ const rank = sourceRank(s.source);
437
+ return rank >= SOURCE_PRIORITY["llm-validated"];
438
+ }).map(filterUnverified);
439
+ if (copy.subcommands.length === 0)
440
+ delete copy.subcommands;
441
+ }
442
+ return copy;
443
+ }
444
+ // src/archaeology/cache.ts
445
+ function getCacheDir(opts) {
446
+ if (opts?.cacheDir)
447
+ return opts.cacheDir;
448
+ const home = process.env.HOME ?? "";
449
+ if (!home)
450
+ throw new Error("HOME is not set; cannot determine cache directory");
451
+ return `${home}/.clitree/cache`;
452
+ }
453
+ function cachePath(cli, opts) {
454
+ const dir = getCacheDir(opts);
455
+ const safe = cli.replace(/[^a-zA-Z0-9_-]/g, "_");
456
+ return `${dir}/${safe}.json`;
457
+ }
458
+ async function loadCache(cli, opts) {
459
+ const path = cachePath(cli, opts);
460
+ const file = Bun.file(path);
461
+ const exists = await file.exists();
462
+ if (!exists)
463
+ return null;
464
+ try {
465
+ const data = await file.json();
466
+ return data;
467
+ } catch {
468
+ return null;
469
+ }
470
+ }
471
+ async function saveCache(cli, cache, opts) {
472
+ const dir = getCacheDir(opts);
473
+ await ensureDir(dir);
474
+ const path = cachePath(cli, opts);
475
+ await Bun.write(path, JSON.stringify(cache, null, 2));
476
+ }
477
+ async function invalidateCache(cli, opts) {
478
+ const path = cachePath(cli, opts);
479
+ const file = Bun.file(path);
480
+ if (await file.exists()) {
481
+ await Bun.$`rm ${path}`.quiet();
482
+ }
483
+ }
484
+ function isCacheStale(cache, currentVersion, ttlDays) {
485
+ if (currentVersion && cache.cliVersion && cache.cliVersion !== currentVersion) {
486
+ return true;
487
+ }
488
+ if (ttlDays) {
489
+ const created = new Date(cache.createdAt).getTime();
490
+ const now = Date.now();
491
+ const ageDays = (now - created) / (1000 * 60 * 60 * 24);
492
+ if (ageDays > ttlDays)
493
+ return true;
494
+ }
495
+ return false;
496
+ }
497
+ async function detectCliVersion(binary) {
498
+ const candidates = ["--version", "-v", "version"];
499
+ for (const arg of candidates) {
500
+ try {
501
+ const proc = Bun.spawn([binary, arg], { stdout: "pipe", stderr: "pipe" });
502
+ const timeout = setTimeout(() => proc.kill(), 3000);
503
+ const [stdout, stderr] = await Promise.all([
504
+ new Response(proc.stdout).text(),
505
+ new Response(proc.stderr).text()
506
+ ]);
507
+ clearTimeout(timeout);
508
+ await proc.exited;
509
+ const output = (stdout || stderr).trim();
510
+ const match = output.match(/v?\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.]+)?/);
511
+ if (match)
512
+ return match[0];
513
+ } catch {}
514
+ }
515
+ return;
516
+ }
517
+ async function ensureDir(dir) {
518
+ try {
519
+ await Bun.$`mkdir -p ${dir}`.quiet();
520
+ } catch {}
521
+ }
522
+ function createEmptyCache(cli, tree, cliVersion) {
523
+ return {
524
+ cli,
525
+ cliVersion,
526
+ createdAt: new Date().toISOString(),
527
+ tree,
528
+ phases: {
529
+ survey: false,
530
+ excavation: false,
531
+ deepDive: false,
532
+ cartography: false
533
+ }
534
+ };
535
+ }
536
+ // src/archaeology/delegate.ts
537
+ class NullDelegate {
538
+ name = "null";
539
+ available = false;
540
+ }
541
+
542
+ class BridgeFileDelegate {
543
+ name = "bridge-file";
544
+ available = true;
545
+ requestsPath;
546
+ responsesPath;
547
+ timeoutMs;
548
+ constructor(opts = {}) {
549
+ const tmp = process.env.TMPDIR ?? "/tmp";
550
+ this.requestsPath = opts.requestsPath ?? `${tmp}/clitree-requests.jsonl`;
551
+ this.responsesPath = opts.responsesPath ?? `${tmp}/clitree-responses.jsonl`;
552
+ this.timeoutMs = opts.timeoutMs ?? 120000;
553
+ }
554
+ async survey(cli, version) {
555
+ const id = crypto.randomUUID();
556
+ await this.writeRequest({ id, kind: "survey", cli, version });
557
+ const response = await this.awaitResponse(id);
558
+ return response;
559
+ }
560
+ async proposeHiddenFlags(cli, subcommand) {
561
+ const id = crypto.randomUUID();
562
+ await this.writeRequest({ id, kind: "deep-dive", cli, subcommand });
563
+ const response = await this.awaitResponse(id);
564
+ return response;
565
+ }
566
+ async proposeConstraints(cli, subcommand) {
567
+ const id = crypto.randomUUID();
568
+ await this.writeRequest({ id, kind: "cartography", cli, subcommand });
569
+ const response = await this.awaitResponse(id);
570
+ return response.constraints ?? [];
571
+ }
572
+ async writeRequest(req) {
573
+ const existing = await this.readFile(this.requestsPath);
574
+ const line = JSON.stringify(req) + `
575
+ `;
576
+ await Bun.write(this.requestsPath, existing + line, { createPath: true });
577
+ }
578
+ async awaitResponse(id) {
579
+ const start = Date.now();
580
+ while (Date.now() - start < this.timeoutMs) {
581
+ const text = await this.readFile(this.responsesPath);
582
+ const lines = text.split(`
583
+ `).filter((l) => l.trim());
584
+ for (const line of lines) {
585
+ try {
586
+ const parsed = JSON.parse(line);
587
+ if (parsed.id === id)
588
+ return parsed.result;
589
+ } catch {}
590
+ }
591
+ await new Promise((r) => setTimeout(r, 250));
592
+ }
593
+ throw new Error(`Timed out waiting for delegate response to request ${id}`);
594
+ }
595
+ async readFile(path) {
596
+ try {
597
+ const file = Bun.file(path);
598
+ if (!await file.exists())
599
+ return "";
600
+ return await file.text();
601
+ } catch {
602
+ return "";
603
+ }
604
+ }
605
+ }
606
+
607
+ class InlineDelegate {
608
+ handlers;
609
+ name = "inline";
610
+ available = true;
611
+ constructor(handlers) {
612
+ this.handlers = handlers;
613
+ }
614
+ async survey(cli, version) {
615
+ if (!this.handlers.survey)
616
+ throw new Error("InlineDelegate: survey handler not provided");
617
+ return this.handlers.survey(cli, version);
618
+ }
619
+ async proposeHiddenFlags(cli, subcommand) {
620
+ if (!this.handlers.proposeHiddenFlags)
621
+ throw new Error("InlineDelegate: proposeHiddenFlags handler not provided");
622
+ return this.handlers.proposeHiddenFlags(cli, subcommand);
623
+ }
624
+ async proposeConstraints(cli, subcommand) {
625
+ if (!this.handlers.proposeConstraints)
626
+ throw new Error("InlineDelegate: proposeConstraints handler not provided");
627
+ return this.handlers.proposeConstraints(cli, subcommand);
628
+ }
629
+ }
630
+ // src/archaeology/orchestrator.ts
631
+ async function runArchaeology(binary, delegate = new NullDelegate, opts = {}) {
632
+ const report = opts.onProgress ?? (() => {});
633
+ const phases = {
634
+ survey: opts.phases?.survey ?? true,
635
+ excavation: opts.phases?.excavation ?? true,
636
+ deepDive: opts.phases?.deepDive ?? true,
637
+ cartography: opts.phases?.cartography ?? false
638
+ };
639
+ report({ phase: "cache", message: "Checking cache..." });
640
+ const version = await detectCliVersion(binary);
641
+ if (!opts.skipCache) {
642
+ const cached = await loadCache(binary, { cacheDir: opts.cacheDir });
643
+ if (cached && !isCacheStale(cached, version, 30)) {
644
+ report({ phase: "done", message: "Loaded from cache" });
645
+ return {
646
+ tree: cached.tree,
647
+ stats: { helpCommands: 0, llmProposed: 0, verified: 0, rejected: 0, unconfirmed: 0, hallucinations: [] },
648
+ fromCache: true
649
+ };
650
+ }
651
+ }
652
+ report({ phase: "excavation", message: `Parsing ${binary} --help recursively...` });
653
+ const helpTree = await parseHelpRecursive(binary, [], opts.maxHelpDepth ?? 2);
654
+ const helpSource = { type: "help", confidence: 1, verifiedAt: new Date().toISOString() };
655
+ const tree = fromCLINode(helpTree, helpSource);
656
+ const helpCommandCount = countCommands(tree);
657
+ let llmProposed = 0;
658
+ const allVerdicts = [];
659
+ if (delegate.available && phases.survey && delegate.survey) {
660
+ report({ phase: "survey", message: "Delegate hypothesis survey..." });
661
+ try {
662
+ const survey = await delegate.survey(binary, version);
663
+ const existingCommands = new Set(tree.subcommands?.map((s) => s.name) ?? []);
664
+ const { confirmed } = await validateSurvey(binary, survey);
665
+ const llmSurveySource = { type: "llm-validated", confidence: 0.8, verifiedAt: new Date().toISOString() };
666
+ for (const sub of confirmed) {
667
+ if (!existingCommands.has(sub)) {
668
+ llmProposed += 1;
669
+ const excavated = await excavateCommand(binary, [sub]);
670
+ if (excavated) {
671
+ const enriched = fromCLINode(excavated, llmSurveySource);
672
+ if (!tree.subcommands)
673
+ tree.subcommands = [];
674
+ tree.subcommands.push(enriched);
675
+ }
676
+ }
677
+ }
678
+ } catch (e) {
679
+ report({ phase: "survey", message: `Survey failed: ${e.message}` });
680
+ }
681
+ }
682
+ if (delegate.available && phases.deepDive && delegate.proposeHiddenFlags && tree.subcommands) {
683
+ const topCommands = tree.subcommands.slice(0, 10);
684
+ report({ phase: "deep-dive", message: `Deep dive into ${topCommands.length} subcommands...` });
685
+ for (const sub of topCommands) {
686
+ try {
687
+ const proposal = await delegate.proposeHiddenFlags(binary, [sub.name]);
688
+ const verdict = await validateDeepDive(binary, proposal);
689
+ allVerdicts.push(verdict);
690
+ const verifiedSet = new Set(verdict.verifiedFlags);
691
+ const unconfirmedSet = new Set(verdict.unconfirmedFlags);
692
+ for (const flag of proposal.flags ?? []) {
693
+ if (verifiedSet.has(flag.name) || opts.includeUnverified && unconfirmedSet.has(flag.name)) {
694
+ llmProposed += 1;
695
+ const source = {
696
+ type: verifiedSet.has(flag.name) ? "llm-validated" : "llm-deep-dive",
697
+ confidence: verifiedSet.has(flag.name) ? 0.9 : 0.6,
698
+ verifiedAt: new Date().toISOString()
699
+ };
700
+ if (!sub.flags)
701
+ sub.flags = [];
702
+ const existing = sub.flags.find((f) => f.name === flag.name);
703
+ if (!existing) {
704
+ sub.flags.push({
705
+ name: flag.name,
706
+ short: flag.short,
707
+ type: flag.type,
708
+ description: flag.description,
709
+ hidden: flag.hidden,
710
+ deprecated: flag.deprecated,
711
+ source
712
+ });
713
+ }
714
+ }
715
+ }
716
+ } catch (e) {
717
+ report({ phase: "deep-dive", message: `Deep dive failed for ${sub.name}: ${e.message}` });
718
+ }
719
+ }
720
+ }
721
+ if (delegate.available && phases.cartography && delegate.proposeConstraints && tree.subcommands) {
722
+ report({ phase: "cartography", message: "Mapping flag constraints..." });
723
+ for (const sub of tree.subcommands.slice(0, 5)) {
724
+ try {
725
+ const constraints = await delegate.proposeConstraints(binary, [sub.name]);
726
+ if (constraints.length > 0) {
727
+ sub.constraints = constraints;
728
+ }
729
+ } catch {}
730
+ }
731
+ }
732
+ report({ phase: "merge", message: "Finalizing tree..." });
733
+ const summary = summarizeValidation(allVerdicts);
734
+ if (!opts.skipCache) {
735
+ const cache = createEmptyCache(binary, tree, version);
736
+ cache.phases = {
737
+ survey: phases.survey,
738
+ excavation: true,
739
+ deepDive: phases.deepDive,
740
+ cartography: phases.cartography
741
+ };
742
+ await saveCache(binary, cache, { cacheDir: opts.cacheDir });
743
+ }
744
+ report({ phase: "done", message: "Archaeology complete" });
745
+ return {
746
+ tree,
747
+ stats: {
748
+ helpCommands: helpCommandCount,
749
+ llmProposed,
750
+ verified: summary.verified,
751
+ rejected: summary.rejected,
752
+ unconfirmed: summary.unconfirmed,
753
+ hallucinations: summary.hallucinations
754
+ },
755
+ fromCache: false
756
+ };
757
+ }
758
+ function countCommands(node) {
759
+ let count = 1;
760
+ if (node.subcommands)
761
+ for (const s of node.subcommands)
762
+ count += countCommands(s);
763
+ return count;
764
+ }
765
+ function enrichedToTree(enriched) {
766
+ return toCLINode(enriched);
767
+ }
768
+ export { toCLINode, fromCLINode, surveyPrompt, excavationPrompt, deepDivePrompt, cartographyPrompt, validationInstructions, extractJsonFromLlmResponse, runHelpCommand, validateSubcommandExists, validateSurvey, excavateCommand, validateDeepDive, summarizeValidation, mergeNodes, filterUnverified, loadCache, saveCache, invalidateCache, isCacheStale, detectCliVersion, createEmptyCache, NullDelegate, BridgeFileDelegate, InlineDelegate, runArchaeology, enrichedToTree };
769
+
770
+ //# debugId=7B12E24C921CA3F964756E2164756E21