lurqrun 0.1.6 → 0.1.7
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/README.md +18 -10
- package/dist/auditHandler-JCOJSUYW.js +23 -0
- package/dist/bin/lurq.js +3090 -23306
- package/dist/browserAuth-B6G27USY.js +95 -0
- package/dist/cache-DKI34SVS.js +12 -0
- package/dist/check-2FDGUOV7.js +192 -0
- package/dist/checkUpgrade-F2WESBPM.js +103 -0
- package/dist/chunk-2VMOWSRY.js +300 -0
- package/dist/chunk-3GHJR7XR.js +2049 -0
- package/dist/chunk-3N3Q3DSW.js +214 -0
- package/dist/chunk-4ESO33J6.js +34 -0
- package/dist/chunk-4F7ZESPO.js +132 -0
- package/dist/chunk-4KCPOMG6.js +68 -0
- package/dist/chunk-4ZYCIMY4.js +155 -0
- package/dist/chunk-63MUJNZL.js +318 -0
- package/dist/chunk-6AAEVQ7I.js +6336 -0
- package/dist/chunk-6EQ3ARXA.js +304 -0
- package/dist/chunk-6L3U7DZ5.js +23 -0
- package/dist/chunk-AHBP533R.js +220 -0
- package/dist/chunk-B43LCXBM.js +223 -0
- package/dist/chunk-BCANRN5B.js +33 -0
- package/dist/chunk-BUALU27S.js +123 -0
- package/dist/chunk-BULZAX2U.js +1554 -0
- package/dist/chunk-BV32UNLL.js +154 -0
- package/dist/chunk-C6GLE626.js +160 -0
- package/dist/chunk-CIDWNUAP.js +145 -0
- package/dist/chunk-CUP36ND3.js +2126 -0
- package/dist/chunk-CW5HJ3LX.js +96 -0
- package/dist/chunk-DB2VF5UD.js +315 -0
- package/dist/chunk-DWAGCNEL.js +210 -0
- package/dist/chunk-EA3DP5HG.js +293 -0
- package/dist/chunk-ELGCAM7P.js +184 -0
- package/dist/chunk-FW6BD2NO.js +1915 -0
- package/dist/chunk-GE37NVLN.js +15 -0
- package/dist/chunk-GPTKSNR6.js +171 -0
- package/dist/chunk-GRJXQBQZ.js +793 -0
- package/dist/chunk-HWBBVLOT.js +92 -0
- package/dist/chunk-I2JUKN6Z.js +24 -0
- package/dist/chunk-I3M2KJXB.js +56 -0
- package/dist/chunk-IQ4SDKKY.js +733 -0
- package/dist/chunk-J5FZYN4A.js +116 -0
- package/dist/chunk-L6N4JFW2.js +263 -0
- package/dist/chunk-LBFGSSF2.js +126 -0
- package/dist/chunk-LEGPB7DO.js +81 -0
- package/dist/chunk-LO35HLQZ.js +219 -0
- package/dist/chunk-LRD4GBYE.js +73 -0
- package/dist/chunk-LUAKDHLZ.js +40 -0
- package/dist/chunk-LYMPYETA.js +965 -0
- package/dist/chunk-M6NYEXCW.js +38 -0
- package/dist/chunk-MHUEVBVO.js +35 -0
- package/dist/chunk-MYGSLK44.js +37 -0
- package/dist/chunk-NH5I4KHH.js +201 -0
- package/dist/chunk-OQS4GUSI.js +1391 -0
- package/dist/chunk-PSX3GIAN.js +176 -0
- package/dist/chunk-Q5VPF4WI.js +26 -0
- package/dist/chunk-QKGYDMWB.js +7409 -0
- package/dist/chunk-QSL6QN5X.js +115 -0
- package/dist/chunk-SHCEBP3J.js +107 -0
- package/dist/chunk-SJOVFF7B.js +201 -0
- package/dist/chunk-ST2X2GYN.js +373 -0
- package/dist/chunk-TNTXWSYA.js +19 -0
- package/dist/chunk-U5AT3TD4.js +2128 -0
- package/dist/chunk-UDVNA7EA.js +483 -0
- package/dist/chunk-VL2E5T2A.js +109 -0
- package/dist/chunk-VLRHFMGJ.js +58 -0
- package/dist/chunk-WHX2ZFCG.js +4048 -0
- package/dist/chunk-WQ23REYT.js +37 -0
- package/dist/chunk-XBIDMHSK.js +15425 -0
- package/dist/chunk-Y5GI7RL3.js +66 -0
- package/dist/client-IRKAMDYZ.js +15 -0
- package/dist/commands-FIL5ET6P.js +911 -0
- package/dist/compat-R3UTFQ5Q.js +201 -0
- package/dist/dashboardIssue-WYYMBFIB.js +192 -0
- package/dist/diff-GZA2774U.js +13 -0
- package/dist/dist-4XXIXHHZ.js +12945 -0
- package/dist/e2b-FVPHDULZ.js +172 -0
- package/dist/extract-NKGKLIVT.js +444 -0
- package/dist/fetch-QYTUJVG2.js +22 -0
- package/dist/firstTouch-QQTJH4QX.js +55 -0
- package/dist/handlers-GEJAVZDK.js +53 -0
- package/dist/http-ETPYMBGD.js +5360 -0
- package/dist/ingestQueue-VKN4KBIF.js +35 -0
- package/dist/install-EK3W2COX.js +20 -0
- package/dist/installSkill-VQJWJVCL.js +52 -0
- package/dist/inventory-5AWSLJYU.js +31 -0
- package/dist/local-XPZMRH5R.js +13 -0
- package/dist/mcp-3JHD5SUZ.js +472 -0
- package/dist/mcpHandlers-QFN4MCFL.js +22 -0
- package/dist/mcpScan-QZHWENIP.js +1871 -0
- package/dist/mcpScanWorkflow-JPYCBGC4.js +81 -0
- package/dist/mcpStack-BYJCZNIV.js +20 -0
- package/dist/packages-THVFMWFP.js +42 -0
- package/dist/plan-V4OEI3J4.js +463 -0
- package/dist/planView-JRXGCG2N.js +79 -0
- package/dist/policy-3P6BGRDN.js +115 -0
- package/dist/references-6N7VEPHT.js +14 -0
- package/dist/release-4U4ABNSJ.js +128 -0
- package/dist/remote-YGCXYI4V.js +32 -0
- package/dist/reportRuns-CKQQYLY4.js +64 -0
- package/dist/sandbox-MEZLAODF.js +39 -0
- package/dist/server-KFUD5X57.js +55 -0
- package/dist/surface-V62GE554.js +141 -0
- package/dist/surface-YL6O67W3.js +36 -0
- package/dist/surfaceHandlers-X2IZKA3E.js +20 -0
- package/dist/typecheck-X7U5ELMR.js +268 -0
- package/dist/types-M5JP2W3D.js +10 -0
- package/dist/uninstall-P7RGBX6G.js +174 -0
- package/dist/upgrade-NQ5IWFZB.js +596 -0
- package/dist/upgradePlan-A4LRKLEG.js +146 -0
- package/dist/userConfig-Z3GQE776.js +20 -0
- package/dist/watch-DGFMWUDL.js +214 -0
- package/package.json +26 -21
- package/templates/agent-rules.md +4 -4
- package/templates/skill-instructions.md +13 -14
- package/dist/bin/lurq.js.map +0 -1
- package/dist/index.d.ts +0 -805
- package/dist/index.js +0 -23570
- package/dist/index.js.map +0 -1
- package/drizzle/0000_confused_fabian_cortez.sql +0 -54
- package/drizzle/0001_aspiring_timeslip.sql +0 -15
- package/drizzle/0002_lyrical_lila_cheney.sql +0 -14
- package/drizzle/0003_violet_giant_girl.sql +0 -14
- package/drizzle/0004_slim_valeria_richards.sql +0 -15
- package/drizzle/0005_petite_luke_cage.sql +0 -12
- package/drizzle/0006_calm_nighthawk.sql +0 -3
- package/drizzle/0007_slimy_naoko.sql +0 -1
- package/drizzle/0008_peaceful_tigra.sql +0 -1
- package/drizzle/0009_smooth_harrier.sql +0 -10
- package/drizzle/0010_curved_amphibian.sql +0 -2
- package/drizzle/0011_compat_provenance_closures.sql +0 -11
- package/drizzle/0012_api_surfaces.sql +0 -9
- package/drizzle/0013_nice_thanos.sql +0 -1
- package/drizzle/0014_luxuriant_malcolm_colcord.sql +0 -10
- package/drizzle/0015_past_fat_cobra.sql +0 -11
- package/drizzle/0016_futuristic_junta.sql +0 -50
- package/drizzle/0017_absent_grandmaster.sql +0 -20
- package/drizzle/0018_rainy_proemial_gods.sql +0 -1
- package/drizzle/0019_dashing_korvac.sql +0 -11
- package/drizzle/0020_normal_vulture.sql +0 -3
- package/drizzle/0021_dazzling_blue_shield.sql +0 -17
- package/drizzle/0022_massive_ghost_rider.sql +0 -24
- package/drizzle/0023_next_shatterstar.sql +0 -1
- package/drizzle/0024_dusty_maelstrom.sql +0 -15
- package/drizzle/0025_gifted_harpoon.sql +0 -5
- package/drizzle/0026_awesome_mordo.sql +0 -1
- package/drizzle/0027_charming_ulik.sql +0 -2
- package/drizzle/0028_romantic_loners.sql +0 -2
- package/drizzle/0029_busy_korvac.sql +0 -16
- package/drizzle/0030_steady_mathemanic.sql +0 -6
- package/drizzle/0031_redundant_maginty.sql +0 -2
- package/drizzle/0032_stack_resolutions.sql +0 -14
- package/drizzle/0033_graceful_tyger_tiger.sql +0 -3
- package/drizzle/0034_uneven_thunderbird.sql +0 -1
- package/drizzle/0035_api_key_scopes.sql +0 -1
- package/drizzle/0036_policy_audit.sql +0 -21
- package/drizzle/0037_subscription_seats.sql +0 -1
- package/drizzle/0038_symbol_offsets.sql +0 -2
- package/drizzle/0039_mcp_runtime_scans.sql +0 -80
- package/drizzle/0040_subscription_overage_and_interval.sql +0 -4
- package/drizzle/0041_account_email.sql +0 -38
- package/drizzle/0042_alert_channels.sql +0 -21
- package/drizzle/meta/0000_snapshot.json +0 -370
- package/drizzle/meta/0001_snapshot.json +0 -482
- package/drizzle/meta/0002_snapshot.json +0 -575
- package/drizzle/meta/0003_snapshot.json +0 -667
- package/drizzle/meta/0004_snapshot.json +0 -769
- package/drizzle/meta/0005_snapshot.json +0 -864
- package/drizzle/meta/0006_snapshot.json +0 -882
- package/drizzle/meta/0007_snapshot.json +0 -876
- package/drizzle/meta/0008_snapshot.json +0 -882
- package/drizzle/meta/0009_snapshot.json +0 -948
- package/drizzle/meta/0010_snapshot.json +0 -969
- package/drizzle/meta/0011_snapshot.json +0 -1049
- package/drizzle/meta/0012_snapshot.json +0 -1115
- package/drizzle/meta/0013_snapshot.json +0 -1121
- package/drizzle/meta/0014_snapshot.json +0 -1190
- package/drizzle/meta/0015_snapshot.json +0 -1280
- package/drizzle/meta/0016_snapshot.json +0 -1683
- package/drizzle/meta/0017_snapshot.json +0 -1834
- package/drizzle/meta/0018_snapshot.json +0 -1834
- package/drizzle/meta/0019_snapshot.json +0 -1909
- package/drizzle/meta/0020_snapshot.json +0 -1921
- package/drizzle/meta/0021_snapshot.json +0 -2045
- package/drizzle/meta/0022_snapshot.json +0 -2246
- package/drizzle/meta/0023_snapshot.json +0 -2252
- package/drizzle/meta/0024_snapshot.json +0 -2375
- package/drizzle/meta/0025_snapshot.json +0 -2407
- package/drizzle/meta/0026_snapshot.json +0 -2413
- package/drizzle/meta/0027_snapshot.json +0 -2425
- package/drizzle/meta/0028_snapshot.json +0 -2438
- package/drizzle/meta/0029_snapshot.json +0 -2546
- package/drizzle/meta/0030_snapshot.json +0 -2586
- package/drizzle/meta/0031_snapshot.json +0 -2598
- package/drizzle/meta/0032_snapshot.json +0 -2699
- package/drizzle/meta/0033_snapshot.json +0 -2712
- package/drizzle/meta/0034_snapshot.json +0 -2719
- package/drizzle/meta/0035_snapshot.json +0 -2726
- package/drizzle/meta/0036_snapshot.json +0 -2876
- package/drizzle/meta/0037_snapshot.json +0 -2883
- package/drizzle/meta/0038_snapshot.json +0 -2895
- package/drizzle/meta/0039_snapshot.json +0 -3501
- package/drizzle/meta/0040_snapshot.json +0 -3527
- package/drizzle/meta/0041_snapshot.json +0 -3824
- package/drizzle/meta/0042_snapshot.json +0 -3960
- package/drizzle/meta/_journal.json +0 -307
- package/src/data/seed.json +0 -253
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createRequire as __lurqCreateRequire } from 'node:module'; const require = __lurqCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
cliSpec
|
|
4
|
+
} from "./chunk-B43LCXBM.js";
|
|
5
|
+
import "./chunk-GPTKSNR6.js";
|
|
6
|
+
import {
|
|
7
|
+
init_esm_shims
|
|
8
|
+
} from "./chunk-I3M2KJXB.js";
|
|
9
|
+
|
|
10
|
+
// src/github/mcpScanWorkflow.ts
|
|
11
|
+
init_esm_shims();
|
|
12
|
+
var MCP_SCAN_WORKFLOW_PATH = ".github/workflows/lurq-mcp-scan.yml";
|
|
13
|
+
var DEFAULT_CRON = "23 6 * * *";
|
|
14
|
+
var CONFIG_PATHS = [".mcp.json", ".cursor/mcp.json", ".vscode/mcp.json", ".windsurf/mcp.json"];
|
|
15
|
+
var VALID_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
16
|
+
function secretNameFor(envName) {
|
|
17
|
+
return envName.toUpperCase().startsWith("GITHUB_") ? `MCP_${envName}` : envName;
|
|
18
|
+
}
|
|
19
|
+
function renderMcpScanWorkflow(opts = {}) {
|
|
20
|
+
const cron = opts.cron ?? DEFAULT_CRON;
|
|
21
|
+
if (cron.trim().split(/\s+/).length !== 5) throw new Error(`cron must have five fields; got "${cron}"`);
|
|
22
|
+
const failOn = opts.failOn ?? "high";
|
|
23
|
+
const issue = opts.githubIssue !== false;
|
|
24
|
+
const secrets = [...new Set(opts.secrets ?? [])].filter((s) => VALID_NAME.test(s) && s !== "LURQ_API_KEY").sort();
|
|
25
|
+
const serverWantsGithubToken = secrets.includes("GITHUB_TOKEN");
|
|
26
|
+
const env = [
|
|
27
|
+
" LURQ_API_KEY: ${{ secrets.LURQ_API_KEY }}",
|
|
28
|
+
...issue && !serverWantsGithubToken ? [" GITHUB_TOKEN: ${{ github.token }}"] : [],
|
|
29
|
+
...secrets.map((s) => ` ${s}: \${{ secrets.${secretNameFor(s)} }}`)
|
|
30
|
+
].join("\n");
|
|
31
|
+
const uv = opts.needsUv ? `
|
|
32
|
+
# A server here launches with uvx.
|
|
33
|
+
- uses: astral-sh/setup-uv@v6
|
|
34
|
+
` : "";
|
|
35
|
+
const permissions = issue ? `# Contents are read-only; nothing the scan reads can change code. The one write
|
|
36
|
+
# keeps the pinned "lurq dashboard" issue current, with server text escaped.
|
|
37
|
+
permissions:
|
|
38
|
+
contents: read
|
|
39
|
+
issues: write` : `# Read-only. The scan reads tool contracts; nothing it reads can write here.
|
|
40
|
+
permissions:
|
|
41
|
+
contents: read`;
|
|
42
|
+
return `# Rescans the MCP servers committed to this repository: what each one exposes,
|
|
43
|
+
# what its tools tell your agents, and what changed since the last scan.
|
|
44
|
+
# Generated by \`lurq mcp-ci\`. Delete this file to stop it.
|
|
45
|
+
name: lurq mcp scan
|
|
46
|
+
|
|
47
|
+
on:
|
|
48
|
+
schedule:
|
|
49
|
+
- cron: '${cron}'
|
|
50
|
+
pull_request:
|
|
51
|
+
paths:
|
|
52
|
+
${CONFIG_PATHS.map((p) => ` - '${p}'`).join("\n")}
|
|
53
|
+
workflow_dispatch: {}
|
|
54
|
+
|
|
55
|
+
${permissions}
|
|
56
|
+
|
|
57
|
+
jobs:
|
|
58
|
+
scan:
|
|
59
|
+
runs-on: ubuntu-latest
|
|
60
|
+
timeout-minutes: 15
|
|
61
|
+
steps:
|
|
62
|
+
- uses: actions/checkout@v6
|
|
63
|
+
- uses: actions/setup-node@v4
|
|
64
|
+
with:
|
|
65
|
+
node-version: 22
|
|
66
|
+
${uv}
|
|
67
|
+
# Add each credential below as a repository secret (Settings \u2192 Secrets).
|
|
68
|
+
# A missing one is reported as "needs config", never as a broken server.
|
|
69
|
+
# --require-upload fails the job when the scan could not be recorded, so a
|
|
70
|
+
# green run always means the dashboard's history has today's scan in it.
|
|
71
|
+
- name: Scan MCP servers
|
|
72
|
+
run: npx -y ${cliSpec()} mcp-scan --project-only --trust-project --require-upload${issue ? " --github-issue" : ""} --fail-on ${failOn}
|
|
73
|
+
env:
|
|
74
|
+
${env}
|
|
75
|
+
`;
|
|
76
|
+
}
|
|
77
|
+
export {
|
|
78
|
+
MCP_SCAN_WORKFLOW_PATH,
|
|
79
|
+
renderMcpScanWorkflow,
|
|
80
|
+
secretNameFor
|
|
81
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createRequire as __lurqCreateRequire } from 'node:module'; const require = __lurqCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
CROWDED_TOOL_COUNT,
|
|
4
|
+
TOKENS_PER_TOOL,
|
|
5
|
+
analyzeStack,
|
|
6
|
+
checkMcpStack,
|
|
7
|
+
isCrowded,
|
|
8
|
+
loadStackMembers
|
|
9
|
+
} from "./chunk-VL2E5T2A.js";
|
|
10
|
+
import "./chunk-6EQ3ARXA.js";
|
|
11
|
+
import "./chunk-SHCEBP3J.js";
|
|
12
|
+
import "./chunk-I3M2KJXB.js";
|
|
13
|
+
export {
|
|
14
|
+
CROWDED_TOOL_COUNT,
|
|
15
|
+
TOKENS_PER_TOOL,
|
|
16
|
+
analyzeStack,
|
|
17
|
+
checkMcpStack,
|
|
18
|
+
isCrowded,
|
|
19
|
+
loadStackMembers
|
|
20
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createRequire as __lurqCreateRequire } from 'node:module'; const require = __lurqCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
ensureSeedEntry,
|
|
4
|
+
finishSyncRun,
|
|
5
|
+
getAllPackageNames,
|
|
6
|
+
getContributionsByOwner,
|
|
7
|
+
getPackageByName,
|
|
8
|
+
getPackageNamesCreatedSince,
|
|
9
|
+
getPackageVersions,
|
|
10
|
+
getSeedTargets,
|
|
11
|
+
getStaleRefreshTargets,
|
|
12
|
+
getTopPackageNames,
|
|
13
|
+
latestVersionsFor,
|
|
14
|
+
reusableFieldsFor,
|
|
15
|
+
stampFirstRequester,
|
|
16
|
+
startSyncRun,
|
|
17
|
+
upsertPackage,
|
|
18
|
+
upsertPackageVersions
|
|
19
|
+
} from "./chunk-NH5I4KHH.js";
|
|
20
|
+
import "./chunk-VLRHFMGJ.js";
|
|
21
|
+
import "./chunk-LYMPYETA.js";
|
|
22
|
+
import "./chunk-MYGSLK44.js";
|
|
23
|
+
import "./chunk-GPTKSNR6.js";
|
|
24
|
+
import "./chunk-I3M2KJXB.js";
|
|
25
|
+
export {
|
|
26
|
+
ensureSeedEntry,
|
|
27
|
+
finishSyncRun,
|
|
28
|
+
getAllPackageNames,
|
|
29
|
+
getContributionsByOwner,
|
|
30
|
+
getPackageByName,
|
|
31
|
+
getPackageNamesCreatedSince,
|
|
32
|
+
getPackageVersions,
|
|
33
|
+
getSeedTargets,
|
|
34
|
+
getStaleRefreshTargets,
|
|
35
|
+
getTopPackageNames,
|
|
36
|
+
latestVersionsFor,
|
|
37
|
+
reusableFieldsFor,
|
|
38
|
+
stampFirstRequester,
|
|
39
|
+
startSyncRun,
|
|
40
|
+
upsertPackage,
|
|
41
|
+
upsertPackageVersions
|
|
42
|
+
};
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import { createRequire as __lurqCreateRequire } from 'node:module'; const require = __lurqCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
buildMermaid,
|
|
4
|
+
layerFor
|
|
5
|
+
} from "./chunk-4F7ZESPO.js";
|
|
6
|
+
import {
|
|
7
|
+
checkCompat,
|
|
8
|
+
latestDataAsOf,
|
|
9
|
+
recommend
|
|
10
|
+
} from "./chunk-BULZAX2U.js";
|
|
11
|
+
import "./chunk-QSL6QN5X.js";
|
|
12
|
+
import "./chunk-M6NYEXCW.js";
|
|
13
|
+
import "./chunk-6L3U7DZ5.js";
|
|
14
|
+
import "./chunk-J5FZYN4A.js";
|
|
15
|
+
import "./chunk-PSX3GIAN.js";
|
|
16
|
+
import {
|
|
17
|
+
assembleMembers,
|
|
18
|
+
getOrFetchPackage,
|
|
19
|
+
inferCategory
|
|
20
|
+
} from "./chunk-FW6BD2NO.js";
|
|
21
|
+
import {
|
|
22
|
+
resolveArchitectureCompat
|
|
23
|
+
} from "./chunk-BUALU27S.js";
|
|
24
|
+
import "./chunk-LRD4GBYE.js";
|
|
25
|
+
import "./chunk-BCANRN5B.js";
|
|
26
|
+
import "./chunk-2VMOWSRY.js";
|
|
27
|
+
import "./chunk-4ESO33J6.js";
|
|
28
|
+
import {
|
|
29
|
+
getSandboxEdges
|
|
30
|
+
} from "./chunk-CW5HJ3LX.js";
|
|
31
|
+
import "./chunk-AHBP533R.js";
|
|
32
|
+
import {
|
|
33
|
+
httpRequest
|
|
34
|
+
} from "./chunk-SJOVFF7B.js";
|
|
35
|
+
import "./chunk-HWBBVLOT.js";
|
|
36
|
+
import "./chunk-NH5I4KHH.js";
|
|
37
|
+
import {
|
|
38
|
+
DEFAULT_ECOSYSTEM,
|
|
39
|
+
isCategory
|
|
40
|
+
} from "./chunk-VLRHFMGJ.js";
|
|
41
|
+
import "./chunk-WQ23REYT.js";
|
|
42
|
+
import {
|
|
43
|
+
packages
|
|
44
|
+
} from "./chunk-LYMPYETA.js";
|
|
45
|
+
import "./chunk-3GHJR7XR.js";
|
|
46
|
+
import {
|
|
47
|
+
getConfig
|
|
48
|
+
} from "./chunk-GRJXQBQZ.js";
|
|
49
|
+
import "./chunk-WHX2ZFCG.js";
|
|
50
|
+
import "./chunk-LUAKDHLZ.js";
|
|
51
|
+
import {
|
|
52
|
+
logger
|
|
53
|
+
} from "./chunk-MYGSLK44.js";
|
|
54
|
+
import "./chunk-GPTKSNR6.js";
|
|
55
|
+
import {
|
|
56
|
+
init_esm_shims
|
|
57
|
+
} from "./chunk-I3M2KJXB.js";
|
|
58
|
+
|
|
59
|
+
// src/mcp/plan.ts
|
|
60
|
+
init_esm_shims();
|
|
61
|
+
import { createHash } from "crypto";
|
|
62
|
+
import { and, eq, inArray } from "drizzle-orm";
|
|
63
|
+
|
|
64
|
+
// src/compat/optimize.ts
|
|
65
|
+
init_esm_shims();
|
|
66
|
+
function conflictsFor(members, sandboxConflicts) {
|
|
67
|
+
const out = resolveArchitectureCompat(members);
|
|
68
|
+
for (let i = 0; i < members.length; i++) {
|
|
69
|
+
for (let j = i + 1; j < members.length; j++) {
|
|
70
|
+
const a = members[i].name;
|
|
71
|
+
const b = members[j].name;
|
|
72
|
+
const key = a <= b ? `${a}|${b}` : `${b}|${a}`;
|
|
73
|
+
if (sandboxConflicts.has(key)) {
|
|
74
|
+
out.push({
|
|
75
|
+
source: "sandbox",
|
|
76
|
+
packages: [a, b],
|
|
77
|
+
detail: `${a} and ${b} are recorded as incompatible (sandbox)`
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
function optimizeStack(slots, sandboxConflicts = /* @__PURE__ */ new Set()) {
|
|
85
|
+
if (slots.some((s) => s.length === 0)) {
|
|
86
|
+
throw new Error("optimizeStack: every slot must have at least one candidate");
|
|
87
|
+
}
|
|
88
|
+
const n = slots.length;
|
|
89
|
+
const budget = 5e4;
|
|
90
|
+
let nodes = 0;
|
|
91
|
+
let exhausted = false;
|
|
92
|
+
let bestSelection = new Array(n).fill(0);
|
|
93
|
+
let bestRegret = conflictsFor(
|
|
94
|
+
slots.map((c) => c[0]).filter((m) => Boolean(m)),
|
|
95
|
+
sandboxConflicts
|
|
96
|
+
).length === 0 ? 0 : Infinity;
|
|
97
|
+
const chosen = new Array(n).fill(0);
|
|
98
|
+
const dfs = (slot, regret) => {
|
|
99
|
+
if (nodes++ > budget) {
|
|
100
|
+
exhausted = true;
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (regret >= bestRegret) return;
|
|
104
|
+
if (slot === n) {
|
|
105
|
+
bestSelection = chosen.slice();
|
|
106
|
+
bestRegret = regret;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const candidates = slots[slot];
|
|
110
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
111
|
+
chosen[slot] = i;
|
|
112
|
+
const assigned = chosen.slice(0, slot + 1).map((idx, s) => slots[s][idx]);
|
|
113
|
+
if (conflictsFor(assigned, sandboxConflicts).length === 0) {
|
|
114
|
+
dfs(slot + 1, regret + i);
|
|
115
|
+
}
|
|
116
|
+
if (nodes > budget) {
|
|
117
|
+
exhausted = true;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
dfs(0, 0);
|
|
123
|
+
const members = bestSelection.map((idx, s) => slots[s][idx]);
|
|
124
|
+
return {
|
|
125
|
+
selection: bestSelection,
|
|
126
|
+
conflicts: conflictsFor(members, sandboxConflicts),
|
|
127
|
+
regret: bestRegret === Infinity ? bestSelection.reduce((a, b) => a + b, 0) : bestRegret,
|
|
128
|
+
bounded: exhausted
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/mcp/plan.ts
|
|
133
|
+
function packageToCandidate(row) {
|
|
134
|
+
return {
|
|
135
|
+
name: row.name,
|
|
136
|
+
category: row.category,
|
|
137
|
+
healthScore: row.healthScore ?? 0,
|
|
138
|
+
qualityScore: row.qualityScore,
|
|
139
|
+
confidence: row.confidence ?? "unproven",
|
|
140
|
+
why: "pinned by you",
|
|
141
|
+
latestVersion: row.latestVersion,
|
|
142
|
+
weeklyDownloads: row.weeklyDownloads,
|
|
143
|
+
lastReleaseAt: row.lastReleaseAt ? row.lastReleaseAt.toISOString() : null,
|
|
144
|
+
repoUrl: row.repoUrl,
|
|
145
|
+
// A pin is the one slot lurq did not choose, which makes these MORE worth
|
|
146
|
+
// saying, not less: the user picked it, so nobody else is going to notice
|
|
147
|
+
// that it has been deprecated since they did.
|
|
148
|
+
deprecated: row.deprecated,
|
|
149
|
+
archived: row.archived,
|
|
150
|
+
advisories: row.advisories?.length ?? 0
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
async function resolvePins(db, using, recommendedNames, requestedByOwnerId = null) {
|
|
154
|
+
const slots = [];
|
|
155
|
+
const notFound = [];
|
|
156
|
+
const pending = [];
|
|
157
|
+
for (const name of new Set(using ?? [])) {
|
|
158
|
+
if (recommendedNames.has(name)) continue;
|
|
159
|
+
const { row, existsOnNpm } = await getOrFetchPackage(db, name, { requestedByOwnerId });
|
|
160
|
+
if (!row) {
|
|
161
|
+
(existsOnNpm ? pending : notFound).push(name);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
slots.push({
|
|
165
|
+
need: `using ${name}`,
|
|
166
|
+
category: row.category,
|
|
167
|
+
layer: layerFor({ label: name, category: row.category }),
|
|
168
|
+
recommended: packageToCandidate(row),
|
|
169
|
+
alternatives: [],
|
|
170
|
+
// fixed: the user chose this, so it never gets swapped
|
|
171
|
+
note: "pinned by you"
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return { slots, notFound, pending };
|
|
175
|
+
}
|
|
176
|
+
var PER_SLOT = 3;
|
|
177
|
+
var MAX_SLOTS = 24;
|
|
178
|
+
async function handlePlan(db, input, ownerId = null) {
|
|
179
|
+
const optimize = input.optimize ?? "balanced";
|
|
180
|
+
const decomposed = input.needs?.length ? { needs: dedupeNeeds(input.needs), source: "needs" } : input.document?.trim() ? await decompose(input.document) : null;
|
|
181
|
+
const hasPins = Boolean(input.using?.length);
|
|
182
|
+
if ((!decomposed || decomposed.needs.length === 0) && !hasPins) {
|
|
183
|
+
return {
|
|
184
|
+
note: "Provide a `document` (a detailed description of your program), a `needs` array, or a `using` list of packages you have already chosen. lurq recommends evidence-scored packages per component, it does not invent an architecture from a bare prompt."
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
const needs = (decomposed?.needs ?? []).slice(0, MAX_SLOTS);
|
|
188
|
+
const source = decomposed?.source ?? "needs";
|
|
189
|
+
let degradedReason = null;
|
|
190
|
+
const safeRecommend = (need, category) => recommend(db, {
|
|
191
|
+
need,
|
|
192
|
+
category,
|
|
193
|
+
limit: PER_SLOT,
|
|
194
|
+
onDegraded: (reason) => {
|
|
195
|
+
degradedReason ??= reason;
|
|
196
|
+
}
|
|
197
|
+
}).catch((err) => {
|
|
198
|
+
logger.warn(`plan: recommend failed for "${need}": ${err.message}`);
|
|
199
|
+
return [];
|
|
200
|
+
});
|
|
201
|
+
const effCat = (n) => n.category ?? inferCategory(n.need) ?? void 0;
|
|
202
|
+
const anchorFamily = familyOf(`${input.document ?? ""} ${needs.map((n) => n.need).join(" ")}`);
|
|
203
|
+
const metaIdx = needs.findIndex((n) => effCat(n) === "meta-framework");
|
|
204
|
+
const anchorIdx = metaIdx >= 0 ? metaIdx : needs.findIndex((n) => effCat(n) === "framework");
|
|
205
|
+
const recs = new Array(needs.length);
|
|
206
|
+
let framework = anchorFamily;
|
|
207
|
+
if (anchorIdx >= 0) {
|
|
208
|
+
recs[anchorIdx] = await safeRecommend(needs[anchorIdx].need, effCat(needs[anchorIdx]));
|
|
209
|
+
framework = recs[anchorIdx][0]?.name ?? anchorFamily;
|
|
210
|
+
}
|
|
211
|
+
const [, dataAsOf] = await Promise.all([
|
|
212
|
+
Promise.all(
|
|
213
|
+
needs.map(async (n, i) => {
|
|
214
|
+
if (i === anchorIdx) return;
|
|
215
|
+
const need = framework ? `${n.need} (for a ${framework} app)` : n.need;
|
|
216
|
+
recs[i] = await safeRecommend(need, effCat(n));
|
|
217
|
+
})
|
|
218
|
+
),
|
|
219
|
+
latestDataAsOf(db)
|
|
220
|
+
]);
|
|
221
|
+
const bundleByName = optimize === "speed" ? await bundleSizes(db, recs.flat()) : /* @__PURE__ */ new Map();
|
|
222
|
+
const taken = /* @__PURE__ */ new Set();
|
|
223
|
+
const recSlots = needs.map((n, i) => {
|
|
224
|
+
const candidates = orderCandidates(recs[i], anchorFamily, optimize, bundleByName);
|
|
225
|
+
const free = candidates.filter((c) => !taken.has(c.name));
|
|
226
|
+
const recommended = free[0] ?? null;
|
|
227
|
+
if (recommended) taken.add(recommended.name);
|
|
228
|
+
const category = n.category ?? recommended?.category ?? inferCategory(n.need);
|
|
229
|
+
return {
|
|
230
|
+
need: n.need,
|
|
231
|
+
category,
|
|
232
|
+
layer: layerFor({ label: recommended?.name ?? n.need, category }),
|
|
233
|
+
recommended,
|
|
234
|
+
alternatives: free.slice(1),
|
|
235
|
+
note: recommended ? void 0 : candidates.length ? "every match for this need is already in the stack for another slot" : "no tracked package matched this need yet"
|
|
236
|
+
};
|
|
237
|
+
});
|
|
238
|
+
const recommendedNames = new Set(
|
|
239
|
+
recSlots.map((s) => s.recommended?.name).filter((n) => Boolean(n))
|
|
240
|
+
);
|
|
241
|
+
const {
|
|
242
|
+
slots: pinnedSlots,
|
|
243
|
+
notFound: pinsNotFound,
|
|
244
|
+
pending: pinsPending
|
|
245
|
+
} = await resolvePins(db, input.using, recommendedNames, ownerId);
|
|
246
|
+
const slots = [...pinnedSlots, ...recSlots];
|
|
247
|
+
const compatibility = await resolveCompat(db, slots);
|
|
248
|
+
const unmatched = [
|
|
249
|
+
...slots.filter((s) => !s.recommended).map((s) => s.need),
|
|
250
|
+
...pinsNotFound.map((n) => `${n} (pinned, but not on the npm registry)`),
|
|
251
|
+
// A pin that exists and is merely being ingested is not an unmatched need —
|
|
252
|
+
// it just has no scores yet. Saying so keeps the user from "fixing" a name
|
|
253
|
+
// that was correct all along.
|
|
254
|
+
...pinsPending.map((n) => `${n} (pinned; being scored now, retry shortly)`)
|
|
255
|
+
];
|
|
256
|
+
const mermaid = buildMermaid(
|
|
257
|
+
slots.filter((s) => s.recommended).map((s) => ({ label: s.recommended.name, category: s.category }))
|
|
258
|
+
);
|
|
259
|
+
return {
|
|
260
|
+
dataAsOf,
|
|
261
|
+
optimize,
|
|
262
|
+
source,
|
|
263
|
+
framework,
|
|
264
|
+
slots,
|
|
265
|
+
unmatched,
|
|
266
|
+
mermaid,
|
|
267
|
+
note: planNote(source, unmatched.length, optimize, framework, degradedReason),
|
|
268
|
+
compatibility
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
function flagSlotConflicts(picks, conflicts) {
|
|
272
|
+
const out = /* @__PURE__ */ new Map();
|
|
273
|
+
for (const c of conflicts) {
|
|
274
|
+
for (const p of picks) {
|
|
275
|
+
if (p.name && c.packages.includes(p.name)) {
|
|
276
|
+
const others = c.packages.filter((n) => n !== p.name);
|
|
277
|
+
out.set(p.need, [.../* @__PURE__ */ new Set([...out.get(p.need) ?? [], ...others])]);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return out;
|
|
282
|
+
}
|
|
283
|
+
var NO_META = (c) => ({
|
|
284
|
+
name: c.name,
|
|
285
|
+
version: c.latestVersion,
|
|
286
|
+
peerDependencies: null,
|
|
287
|
+
peerDependenciesMeta: null,
|
|
288
|
+
engines: null
|
|
289
|
+
});
|
|
290
|
+
async function resolveCompat(db, slots) {
|
|
291
|
+
const eligible = slots.filter((s) => s.recommended);
|
|
292
|
+
if (eligible.length < 2) return null;
|
|
293
|
+
try {
|
|
294
|
+
const allNames = [
|
|
295
|
+
...new Set(eligible.flatMap((s) => [s.recommended, ...s.alternatives].map((c) => c.name)))
|
|
296
|
+
];
|
|
297
|
+
const [{ members }, edges] = await Promise.all([
|
|
298
|
+
assembleMembers(db, allNames),
|
|
299
|
+
getSandboxEdges(db, allNames)
|
|
300
|
+
]);
|
|
301
|
+
const metaByName = new Map(members.map((m) => [m.name, m]));
|
|
302
|
+
const sandboxConflicts = new Set(
|
|
303
|
+
edges.filter((e) => e.status === "conflict").map((e) => `${e.packageA}|${e.packageB}`)
|
|
304
|
+
);
|
|
305
|
+
const slotCandidates = eligible.map(
|
|
306
|
+
(s) => [s.recommended, ...s.alternatives].map((c) => metaByName.get(c.name) ?? NO_META(c))
|
|
307
|
+
);
|
|
308
|
+
const { selection } = optimizeStack(slotCandidates, sandboxConflicts);
|
|
309
|
+
eligible.forEach((s, i) => {
|
|
310
|
+
const idx = selection[i] ?? 0;
|
|
311
|
+
if (idx <= 0) return;
|
|
312
|
+
const all = [s.recommended, ...s.alternatives];
|
|
313
|
+
const chosen = all[idx];
|
|
314
|
+
if (!chosen) return;
|
|
315
|
+
s.recommended = chosen;
|
|
316
|
+
s.alternatives = all.filter((c) => c.name !== chosen.name);
|
|
317
|
+
s.swappedFrom = all[0].name;
|
|
318
|
+
});
|
|
319
|
+
} catch (err) {
|
|
320
|
+
logger.warn(`plan: compat optimization failed: ${String(err)}`);
|
|
321
|
+
}
|
|
322
|
+
const compat = await checkCompat(
|
|
323
|
+
db,
|
|
324
|
+
eligible.map((s) => s.recommended.name)
|
|
325
|
+
).catch(() => null);
|
|
326
|
+
if (compat) {
|
|
327
|
+
const flags = flagSlotConflicts(
|
|
328
|
+
slots.map((s) => ({ need: s.need, name: s.recommended?.name ?? null })),
|
|
329
|
+
compat.conflicts
|
|
330
|
+
);
|
|
331
|
+
for (const s of slots) {
|
|
332
|
+
const cw = flags.get(s.need);
|
|
333
|
+
s.conflictsWith = cw?.length ? cw : void 0;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return compat;
|
|
337
|
+
}
|
|
338
|
+
function planNote(source, unmatched, optimize, framework, degraded) {
|
|
339
|
+
const base = source === "heuristic" ? "Components were extracted from your document with a keyword heuristic (no summary LLM configured), coarse; pass a `needs` array or set SUMMARY_API_KEY for sharper decomposition." : source === "llm" ? "Components were extracted from your document by the summary model." : "Components taken from the supplied needs.";
|
|
340
|
+
const grounding = " Each package is recommended from lurq\u2019s scored index, a labeled, evidence-backed starting point, not a validated architecture.";
|
|
341
|
+
const ctx = framework ? ` Anchored to the ${framework} ecosystem so sibling libraries stay coherent across the stack.` : "";
|
|
342
|
+
const tail = unmatched ? ` ${unmatched} need(s) had no tracked match (listed in \`unmatched\`).` : "";
|
|
343
|
+
const opt = optimize === "speed" ? " Ranking favored the lightest-bundle option per slot." : "";
|
|
344
|
+
const degradedWarning = degraded ? `DEGRADED: semantic search was unavailable (${degraded}), so these picks come from keyword matching alone and are materially less reliable than usual. Treat them as a shortlist to verify, and re-run when search recovers. ` : "";
|
|
345
|
+
return degradedWarning + base + grounding + ctx + opt + tail;
|
|
346
|
+
}
|
|
347
|
+
function dedupeNeeds(needs) {
|
|
348
|
+
const seen = /* @__PURE__ */ new Map();
|
|
349
|
+
for (const n of needs) {
|
|
350
|
+
const key = n.need.trim().toLowerCase();
|
|
351
|
+
if (!key) continue;
|
|
352
|
+
if (!seen.has(key)) seen.set(key, { need: n.need.trim(), category: n.category });
|
|
353
|
+
}
|
|
354
|
+
return [...seen.values()];
|
|
355
|
+
}
|
|
356
|
+
var FAMILY_TOKENS = [
|
|
357
|
+
{ family: "react", re: /\breact\b|preact/ },
|
|
358
|
+
{ family: "vue", re: /\bvue\b|nuxt/ },
|
|
359
|
+
{ family: "angular", re: /angular/ },
|
|
360
|
+
{ family: "svelte", re: /svelte/ },
|
|
361
|
+
{ family: "solid", re: /\bsolid(-?js)?\b/ }
|
|
362
|
+
];
|
|
363
|
+
function familyOf(name) {
|
|
364
|
+
const n = name.toLowerCase();
|
|
365
|
+
for (const f of FAMILY_TOKENS) if (f.re.test(n)) return f.family;
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
function orderCandidates(cands, anchorFamily, optimize, bundleByName) {
|
|
369
|
+
const coherence = (c) => {
|
|
370
|
+
const fam = familyOf(c.name);
|
|
371
|
+
if (!fam) return 0;
|
|
372
|
+
return fam === anchorFamily ? 1 : -1;
|
|
373
|
+
};
|
|
374
|
+
return [...cands].sort((a, b) => {
|
|
375
|
+
if (anchorFamily) {
|
|
376
|
+
const d = coherence(b) - coherence(a);
|
|
377
|
+
if (d) return d;
|
|
378
|
+
}
|
|
379
|
+
if (optimize === "speed") {
|
|
380
|
+
return (bundleByName.get(a.name) ?? Infinity) - (bundleByName.get(b.name) ?? Infinity);
|
|
381
|
+
}
|
|
382
|
+
return 0;
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
async function bundleSizes(db, candidates) {
|
|
386
|
+
const names = [...new Set(candidates.map((c) => c.name))];
|
|
387
|
+
if (names.length === 0) return /* @__PURE__ */ new Map();
|
|
388
|
+
const rows = await db.select({ name: packages.name, bundle: packages.bundleMinGzipKb }).from(packages).where(and(inArray(packages.name, names), eq(packages.ecosystem, DEFAULT_ECOSYSTEM)));
|
|
389
|
+
return new Map(rows.filter((r) => r.bundle != null).map((r) => [r.name, r.bundle]));
|
|
390
|
+
}
|
|
391
|
+
async function decompose(document) {
|
|
392
|
+
const config = getConfig();
|
|
393
|
+
if (config.SUMMARY_PROVIDER === "openai" && config.SUMMARY_API_KEY) {
|
|
394
|
+
const llm = await decomposeWithLlm(
|
|
395
|
+
document,
|
|
396
|
+
config.SUMMARY_API_KEY,
|
|
397
|
+
config.SUMMARY_MODEL,
|
|
398
|
+
config.SUMMARY_BASE_URL
|
|
399
|
+
).catch((err) => {
|
|
400
|
+
logger.warn(`plan: LLM decomposition failed, using heuristic: ${err.message}`);
|
|
401
|
+
return null;
|
|
402
|
+
});
|
|
403
|
+
if (llm?.length) return { needs: dedupeNeeds(llm), source: "llm" };
|
|
404
|
+
}
|
|
405
|
+
return { needs: decomposeHeuristic(document), source: "heuristic" };
|
|
406
|
+
}
|
|
407
|
+
var DECOMPOSE_SYSTEM = "You break a software project description into the distinct technical components that each need a library. Return ONLY components the project actually requires, grounded in the description. Respond with a JSON object.";
|
|
408
|
+
async function decomposeWithLlm(document, apiKey, model, baseUrl) {
|
|
409
|
+
const prompt = [
|
|
410
|
+
"Project description:",
|
|
411
|
+
document.slice(0, 8e3),
|
|
412
|
+
"",
|
|
413
|
+
'Return JSON: { "needs": [ { "need": "<one phrase describing a component that needs a library>", "category": "<optional taxonomy hint or empty>" } ] }.',
|
|
414
|
+
"One entry per distinct component (e.g. routing, validation, ORM, HTTP client). Omit anything not implied by the description."
|
|
415
|
+
].join("\n");
|
|
416
|
+
const { data } = await httpRequest(`${baseUrl.replace(/\/$/, "")}/chat/completions`, {
|
|
417
|
+
host: new URL(baseUrl).host,
|
|
418
|
+
method: "POST",
|
|
419
|
+
ttlMs: 24 * 60 * 60 * 1e3,
|
|
420
|
+
// Hash the FULL document — length + a 64-char prefix collide for same-length
|
|
421
|
+
// edits or shared templated headers, which would serve a stale decomposition.
|
|
422
|
+
cacheKey: `openai-plan ${model} ${createHash("sha1").update(document).digest("hex")}`,
|
|
423
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
424
|
+
body: JSON.stringify({
|
|
425
|
+
model,
|
|
426
|
+
messages: [
|
|
427
|
+
{ role: "system", content: DECOMPOSE_SYSTEM },
|
|
428
|
+
{ role: "user", content: prompt }
|
|
429
|
+
],
|
|
430
|
+
response_format: { type: "json_object" },
|
|
431
|
+
temperature: 0.2
|
|
432
|
+
})
|
|
433
|
+
});
|
|
434
|
+
const content = data?.choices?.[0]?.message?.content;
|
|
435
|
+
const parsed = content ? JSON.parse(content) : {};
|
|
436
|
+
const raw = Array.isArray(parsed?.needs) ? parsed.needs : [];
|
|
437
|
+
return raw.map((n) => {
|
|
438
|
+
const need = typeof n?.need === "string" ? n.need.trim() : "";
|
|
439
|
+
if (!need) return null;
|
|
440
|
+
const cat = typeof n?.category === "string" && isCategory(n.category) ? n.category : void 0;
|
|
441
|
+
return { need, category: cat };
|
|
442
|
+
}).filter(Boolean);
|
|
443
|
+
}
|
|
444
|
+
function decomposeHeuristic(document) {
|
|
445
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
446
|
+
for (const rawLine of document.split("\n")) {
|
|
447
|
+
const line = rawLine.replace(/^[#>*\-\s]+/, "").replace(/^\d+\.\s+/, "").trim();
|
|
448
|
+
if (line.length < 3) continue;
|
|
449
|
+
const category = inferCategory(line);
|
|
450
|
+
if (category && !byCategory.has(category)) {
|
|
451
|
+
byCategory.set(category, line.slice(0, 120));
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return [...byCategory.entries()].map(([category, need]) => ({ need, category }));
|
|
455
|
+
}
|
|
456
|
+
export {
|
|
457
|
+
decomposeHeuristic,
|
|
458
|
+
familyOf,
|
|
459
|
+
flagSlotConflicts,
|
|
460
|
+
handlePlan,
|
|
461
|
+
orderCandidates,
|
|
462
|
+
resolvePins
|
|
463
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { createRequire as __lurqCreateRequire } from 'node:module'; const require = __lurqCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
init_esm_shims
|
|
4
|
+
} from "./chunk-I3M2KJXB.js";
|
|
5
|
+
|
|
6
|
+
// src/cli/planView.ts
|
|
7
|
+
init_esm_shims();
|
|
8
|
+
function esc(s) {
|
|
9
|
+
return s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
10
|
+
}
|
|
11
|
+
function slotRows(plan) {
|
|
12
|
+
return plan.slots.map((s) => {
|
|
13
|
+
const rec = s.recommended;
|
|
14
|
+
const name = rec ? rec.repoUrl ? `<a href="${esc(rec.repoUrl)}" target="_blank" rel="noreferrer">${esc(rec.name)}</a>` : esc(rec.name) : '<span class="muted">no match</span>';
|
|
15
|
+
const alts = s.alternatives.map((a) => esc(a.name)).join(", ") || '<span class="muted">-</span>';
|
|
16
|
+
return `<tr>
|
|
17
|
+
<td>${esc(s.need)}</td>
|
|
18
|
+
<td><span class="layer">${esc(s.layer)}</span></td>
|
|
19
|
+
<td class="pkg">${name}</td>
|
|
20
|
+
<td class="num">${rec ? rec.healthScore : "-"}</td>
|
|
21
|
+
<td>${rec ? esc(rec.confidence) : "-"}</td>
|
|
22
|
+
<td class="alts">${alts}</td>
|
|
23
|
+
</tr>`;
|
|
24
|
+
}).join("\n");
|
|
25
|
+
}
|
|
26
|
+
function renderPlanHtml(plan) {
|
|
27
|
+
return `<!doctype html>
|
|
28
|
+
<html lang="en">
|
|
29
|
+
<head>
|
|
30
|
+
<meta charset="utf-8" />
|
|
31
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
32
|
+
<title>lurq plan: roadmap</title>
|
|
33
|
+
<style>
|
|
34
|
+
:root { color-scheme: dark; }
|
|
35
|
+
* { box-sizing: border-box; }
|
|
36
|
+
body { margin: 0; font: 15px/1.5 ui-sans-serif, system-ui, -apple-system, sans-serif;
|
|
37
|
+
background: #0b0b0f; color: #e7e7ea; padding: 2.5rem clamp(1rem, 5vw, 4rem); }
|
|
38
|
+
h1 { font-size: 1.5rem; margin: 0 0 .25rem; }
|
|
39
|
+
h2 { font-size: 1rem; text-transform: uppercase; letter-spacing: .08em; color: #9a9aa6; margin: 2.5rem 0 .75rem; }
|
|
40
|
+
.sub { color: #9a9aa6; margin: 0 0 1rem; max-width: 70ch; }
|
|
41
|
+
.diagram { background: #141419; border: 1px solid #26262e; border-radius: 14px; padding: 1.5rem; overflow: auto; }
|
|
42
|
+
table { width: 100%; border-collapse: collapse; margin-top: .5rem; }
|
|
43
|
+
th, td { text-align: left; padding: .55rem .75rem; border-bottom: 1px solid #1e1e25; vertical-align: top; }
|
|
44
|
+
th { font-size: .78rem; text-transform: uppercase; letter-spacing: .05em; color: #9a9aa6; }
|
|
45
|
+
.pkg a { color: #d98cff; text-decoration: none; } .pkg a:hover { text-decoration: underline; }
|
|
46
|
+
.num { text-align: right; font-variant-numeric: tabular-nums; }
|
|
47
|
+
.muted { color: #6a6a76; } .alts { color: #b8b8c2; font-size: .92rem; }
|
|
48
|
+
.layer { font-size: .8rem; color: #8ad; background: #15202b; border-radius: 6px; padding: .1rem .45rem; }
|
|
49
|
+
footer { margin-top: 2.5rem; color: #6a6a76; font-size: .85rem; }
|
|
50
|
+
</style>
|
|
51
|
+
</head>
|
|
52
|
+
<body>
|
|
53
|
+
<h1>lurq plan <span class="muted">\xB7 ${esc(plan.optimize)} \xB7 ${esc(plan.source)}${plan.framework ? ` \xB7 ${esc(plan.framework)} ecosystem` : ""}</span></h1>
|
|
54
|
+
<p class="sub">${esc(plan.note)}</p>
|
|
55
|
+
|
|
56
|
+
<h2>Roadmap</h2>
|
|
57
|
+
<div class="diagram"><pre class="mermaid">${esc(plan.mermaid)}</pre></div>
|
|
58
|
+
|
|
59
|
+
<h2>Components</h2>
|
|
60
|
+
<table>
|
|
61
|
+
<thead><tr><th>Component</th><th>Layer</th><th>Recommended</th><th>Health</th><th>Confidence</th><th>Alternatives</th></tr></thead>
|
|
62
|
+
<tbody>
|
|
63
|
+
${slotRows(plan)}
|
|
64
|
+
</tbody>
|
|
65
|
+
</table>
|
|
66
|
+
|
|
67
|
+
<footer>data as of ${esc(plan.dataAsOf)} \xB7 generated by lurq</footer>
|
|
68
|
+
|
|
69
|
+
<script type="module">
|
|
70
|
+
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
|
|
71
|
+
mermaid.initialize({ startOnLoad: true, theme: 'dark' });
|
|
72
|
+
</script>
|
|
73
|
+
</body>
|
|
74
|
+
</html>
|
|
75
|
+
`;
|
|
76
|
+
}
|
|
77
|
+
export {
|
|
78
|
+
renderPlanHtml
|
|
79
|
+
};
|